This R Markdown Notebook illustrates the techniques used in the paper “Boosting insights in insurance tariff plans with tree-based machine learning methods” via code examples. The full paper is available on arXiv and is published in the North American Actuarial Journal.
MTPL portfolio
We study a motor third party liability (MTPL) portfolio. Details on the data are available in Section 4.1 of the paper.
mtpl_data <- readRDS('mtpl_data.rds')
str(mtpl_data)
'data.frame': 163212 obs. of 18 variables:
$ id : int 1 2 3 4 5 6 7 8 9 10 ...
$ expo : num 1 1 1 1 0.0466 ...
$ claim : Factor w/ 2 levels "0","1": 2 1 1 1 2 1 2 1 1 1 ...
$ nclaims : int 1 0 0 0 1 0 1 0 0 0 ...
$ amount : num 1618 0 0 0 156 ...
$ average : num 1618 NaN NaN NaN 156 ...
$ coverage: Factor w/ 3 levels "TPL","TPL+","TPL++": 1 2 1 1 1 1 3 1 3 2 ...
$ ageph : int 50 64 60 77 28 26 26 58 59 34 ...
$ sex : Factor w/ 2 levels "female","male": 2 1 2 2 1 2 2 1 2 2 ...
$ bm : int 5 5 0 0 9 11 11 11 0 7 ...
$ power : int 77 66 70 57 70 70 55 47 98 74 ...
$ agec : int 12 3 10 15 7 12 8 14 3 6 ...
$ fuel : Factor w/ 2 levels "gasoline","diesel": 1 1 2 1 1 1 1 1 1 1 ...
$ use : Factor w/ 2 levels "private","work": 1 1 1 1 1 1 1 1 1 1 ...
$ fleet : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
$ postcode: int 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 ...
$ long : num 4.36 4.36 4.36 4.36 4.36 ...
$ lat : num 50.8 50.8 50.8 50.8 50.8 ...
The following variables are used as predictor features, see Table 7 in Appendix A of the paper for a description.
features <- c('coverage', 'fuel', 'sex', 'use', 'fleet',
'ageph', 'power', 'agec', 'bm',
'long', 'lat')
Train and test split
We follow the same data partition approach as discussed in the “Cross-validation” paragraph of Section 3.3 in the paper. It partitions the data in six subsets and allows us to replicate the summary statistics of Table 2 as follows:
# Add the fold indicator
mtpl_data <- mtpl_data %>% dplyr::arrange(nclaims, amount, expo) %>%
dplyr::mutate(fold = paste0('data', rep(seq_len(6), length = nrow(mtpl_data))))
# Calculate summary statistics
mtpl_data %>% dplyr::group_by(fold) %>%
dplyr::summarise(sum(nclaims) / sum(expo),
sum(amount) / sum(nclaims))
In this Notebook we assign \(\mathcal{D}_3\) as the test set and the collection \(\{ \mathcal{D}_1, \mathcal{D}_2, \mathcal{D}_4, \mathcal{D}_5, \mathcal{D}_6 \}\) as the training set. This matches the setup of “Data fold 3” in Figure 3 of the paper with the training data in blue and the test data in red.
# Subset the training data
mtpl_trn <- mtpl_data %>% dplyr::filter(fold != 'data3')
# Subset the test data
mtpl_tst <- mtpl_data %>% dplyr::filter(fold == 'data3')
This results in 136010/27202 observations in the train/test data respectively. In the following modeling sections, we use the hyper-parameter settings from Table 1 and the optimal tuning parameter settings from Table 3 for data fold 3.
Frequency modeling
We start by modeling claim frequency, i.e., the number of claims a policyholder is expected to file with the insurer. It is important to account for the period of exposure-to-risk, i.e., the fraction of the year that a policyholder was covered by the policy and therefore exposed to the risk of filing a claim. The number of claims and exposure period are available in mtpl_data as the variables nclaims and expo respectively:
gridExtra::grid.arrange(
ggplot(mtpl_data, aes(x = nclaims)) +
geom_bar(aes(y = (..count..)/sum(..count..)), col = 'black') +
labs(y = 'Proportion') + scale_y_continuous(labels = scales::percent),
ggplot(mtpl_data, aes(x = expo)) +
geom_histogram(aes(y = (..count..)/sum(..count..)), binwidth = 0.05, col = 'black') +
labs(y = 'Proportion') + scale_y_continuous(labels = scales::percent),
ncol = 2
)

Regression tree
We fit a Poisson regression tree for claim frequency with the rpart() function and method = 'poisson' from the distRforest package (available from Roel Henckaerts’s GitHub):
# devtools::install_github('henckr/distRforest')
library(distRforest)
tree_freq <- distRforest::rpart(
formula = as.formula(paste('cbind(expo, nclaims) ~', paste(features, collapse = ' + '))),
data = mtpl_trn,
method = 'poisson',
parms = list(shrink = 0.125), # gamma in Table 3
control = rpart.control(cp = 1.1e-4, # cp in Table 3
minbucket = 0.01 * nrow(mtpl_trn), # kappa in Table 1
xval = 0,
maxcompete = 0,
maxsurrogate = 0)
)
Note: distRforest is an extension of rpart and allows for severity distributions and random forest generation.
Random forest
We fit a Poisson random forest for claim frequency with the rforest() function and method = 'poisson' from the distRforest package (available from Roel Henckaerts’s GitHub):
set.seed(54321)
rf_freq <- distRforest::rforest(
formula = as.formula(paste('cbind(expo, nclaims) ~', paste(features, collapse = ' + '))),
data = mtpl_trn,
method = 'poisson',
ntrees = 400, # T in Table 3
ncand = 8, # m in Table 3
subsample = 0.75, # delta in Table 1
parms = list(shrink = 0.25), # gamma in Table 1
control = rpart.control(cp = 0, # cp in Table 1
minbucket = 0.01 * 0.75 * nrow(mtpl_trn), # kappa * delta in Table 1
xval = 0,
maxcompete = 0,
maxsurrogate = 0),
red_mem = TRUE # reduces the memory footprint of individual rpart trees
)
Gradient boosting machine
We fit a Poisson GBM for claim frequency with the gbm() function and distribution = 'poisson' from the gbm package (available from Harry Southworth’s GitHub):
# devtools::install_github('harrysouthworth/gbm')
library(gbm)
set.seed(54321)
gbm_freq <- gbm(
formula = as.formula(paste('nclaims ~ offset(log(expo)) +', paste(features, collapse = ' + '))),
data = mtpl_trn,
distribution = 'poisson',
n.trees = 1400, # T in Table 3
interaction.depth = 5, # d in Table 3
shrinkage = 0.01, # lambda in Table 1
bag.fraction = 0.75, # delta in Table 1
n.minobsinnode = 0.01 * 0.75 * nrow(mtpl_trn), # kappa * delta in Table 1
verbose = FALSE
)
Note: Unlike the CRAN version of gbm, HS’s implementation allows for a gamma distribution for claim severity.
Severity modeling
Next, we also model claim severity, i.e., the expected loss amount for a claim if it gets filed. The variable amount in mtpl_data contains the total loss amount over all claims filed by a policyholder. In order to get an estimate of the individual claim severity, the variable average contains the total loss amount divided by the number of claims. Only the observations containing a claim can be used to model claim severity:
# Only retain the claims
mtpl_trn_claims <- mtpl_trn %>% dplyr::filter(nclaims > 0)
# Plot the density of all observations and those below 10 000 Euro
gridExtra::grid.arrange(
ggplot(mtpl_trn_claims, aes(x = average)) +
geom_density(adjust = 3, col = 'black', fill = 'gray') +
labs(y = 'Density'),
ggplot(mtpl_trn_claims, aes(x = average)) +
geom_density(adjust = 3, col = 'black', fill = 'gray') +
labs(y = 'Density') + xlim(0, 1e4),
ncol = 2
)

Regression tree
We fit a gamma regression tree for claim severity with the rpart() function and method = 'gamma' from the distRforest package (available from Roel Henckaerts’s GitHub):
tree_sev <- distRforest::rpart(
formula = as.formula(paste('average ~', paste(features, collapse = ' + '))),
data = mtpl_trn_claims,
weights = nclaims,
method = 'gamma',
control = rpart.control(cp = 3.7e-3, # cp in Table 3
minbucket = 0.01 * nrow(mtpl_trn_claims), # kappa in Table 1
xval = 0,
maxcompete = 0,
maxsurrogate = 0)
)
Random forest
We fit a gamma random forest for claim severity with the rforest() function and method = 'gamma' from the distRforest package (available from Roel Henckaerts’s GitHub):
set.seed(54321)
rf_sev <- distRforest::rforest(
formula = as.formula(paste('average ~', paste(features, collapse = ' + '))),
data = mtpl_trn_claims,
weights = nclaims,
method = 'gamma',
ntrees = 600, # T in Table 3
ncand = 1, # m in Table 3
subsample = 0.75, # delta in Table 1
control = rpart.control(cp = 0, # cp in Table 1
minbucket = 0.01 * 0.75 * nrow(mtpl_trn_claims), # kappa * delta in Table 1
xval = 0,
maxcompete = 0,
maxsurrogate = 0),
red_mem = TRUE # reduces the memory footprint of individual rpart trees
)
Gradient boosting machine
We fit a gamma GBM for claim severity with the gbm() function and distribution = 'gamma' from the gbm package (available from Harry Southworth’s GitHub):
set.seed(54321)
gbm_sev <- gbm(
formula = as.formula(paste('average ~', paste(features, collapse = ' + '))),
data = mtpl_trn_claims,
weights = nclaims,
distribution = 'gamma',
n.trees = 500, # T in Table 3
interaction.depth = 1, # d in Table 3
shrinkage = 0.01, # lambda in Table 1
bag.fraction = 0.75, # delta in Table 1
n.minobsinnode = 0.01 * 0.75 * nrow(mtpl_trn_claims), # kappa * delta in Table 1
verbose = FALSE
)
Prediction functions
Now all ML models are fit, we can start analyzing them. To streamline this process, we define a predict function predict_model that can be applied to the different models in a uniform way:
# Generic prediction function
predict_model <- function(object, newdata) UseMethod('predict_model')
# Prediction function for a regression tree
predict_model.rpart <- function(object, newdata) {
predict(object, newdata, type = 'vector')
}
# Prediction function for a random forest
predict_model.rforest <- function(object, newdata) {
predict(object, newdata)
}
# Prediction function for a GBM
predict_model.gbm <- function(object, newdata) {
predict(object, newdata, n.trees = object$n.trees, type = 'response')
}
Model interpretation
We now focus on extracting insights from the ML models with variable importance scores, partial dependence plots (PDPs) and individual conditional expectations (ICEs). Details are available in Sections 3.4 and 4.3 of the paper.
Variable importance
A first interesting insight is to know which features are important according to your model. This information can be obtained in the following way for the different models considered:
- regression tree: access importance scores of an
rpart object via object$variable.importance
- random forest: apply the function
distRforest::importance_rforest to the rforest object
- gradient boosting machine: apply the function
summary (with optional arguments) to the gbm object
These three options are coded in the function var_imp below to generate a uniform output. The function plot_var_imp takes the calculated importance scores and shows the results in a ggplot bar chart.
var_imp <- function(object) {
# Calculate non-normalized importance scores based on the model class
switch(class(object)[1],
'rpart' = data.frame(variable = names(object$variable.importance),
importance = object$variable.importance),
'rforest' = object %>% distRforest::importance_rforest() %>%
dplyr::select(variable, importance),
'gbm' = object %>% summary(plotit = FALSE, normalize = FALSE) %>%
setNames(c('variable', 'importance'))
) %>%
# Normalize the scores to sum to one
dplyr::mutate(scale_sum = round(importance / sum(importance), digits = 4))
}
plot_var_imp <- function(data) {
data %>% ggplot(aes(x = reorder(variable, scale_sum), y = scale_sum)) +
geom_bar(stat = 'identity') + coord_flip() +
labs(x = '', y = 'importance')
}
The variable importance scores for the three frequency ML models are shown below. These are the green bars for data fold 3 in the left panels of Figure 5 in the paper.
gridExtra::grid.arrange(tree_freq %>% var_imp %>% plot_var_imp,
rf_freq %>% var_imp %>% plot_var_imp,
gbm_freq %>% var_imp %>% plot_var_imp,
ncol = 3)

The variable importance scores for the three severity ML models are shown below. These are the green bars for data fold 3 in the right panels of Figure 5 in the paper.
gridExtra::grid.arrange(tree_sev %>% var_imp %>% plot_var_imp,
rf_sev %>% var_imp %>% plot_var_imp,
gbm_sev %>% var_imp %>% plot_var_imp,
ncol = 3)

PDPs
We use partial dependence plots (PDPs) to get an insight on the relation between a feature and the target. The function par_dep performs the essential steps to generate such a PD effect. The following steps are performed for each value in a predefined grid of the variable of interest:
- use the original training data (or a subset to speedup calculations)
- change the value of the variable of interest to the current value in the grid for all observations
- predict the model on this altered data set
- calculate the mean of all these predictions to get the PD effect for the current grid value
par_dep <- function(object, data, grid) {
# Initialize a vector to save the effect
pd_effect <- rep(0, nrow(grid))
# Iterate over the grid values to calculate the effect
for (i in seq_len(length(pd_effect))) {
pd_effect[i] <-
data %>%
dplyr::mutate(!! names(grid) := grid[i, ]) %>%
predict_model(object, newdata = .) %>%
mean()
}
return(pd_effect)
}
We will now use this function to generate the PD effect for the age of the policyholder in the frequency models:
# Use a random sample of the training observations
set.seed(54321)
mtpl_trn_sample <- mtpl_trn[sample(seq_len(nrow(mtpl_trn)), size = 10000), ]
# Define the grid for the ages
grid_ageph <- data.frame('ageph' = 18:90)
# Calculate the PD effect for each ML model
grid_ageph <- grid_ageph %>%
dplyr::mutate(tree = tree_freq %>% par_dep(data = mtpl_trn_sample,
grid = grid_ageph),
rf = rf_freq %>% par_dep(data = mtpl_trn_sample,
grid = grid_ageph),
gbm = gbm_freq %>% par_dep(data = mtpl_trn_sample,
grid = grid_ageph))
After some reshaping we can plot these effects on top of each other. The effects for the tree and the gbm correspond to the green lines in the bottom panels of Figure 6.
grid_ageph %>% reshape2::melt(id.vars = 'ageph',
value.name = 'pd',
variable.name = 'method') %>%
ggplot(aes(x = ageph, y = pd)) +
geom_line(aes(group = method, colour = method))

ICEs
An individual conditional expectation (ICE) curve is generated in a very comparable way to a PDP. The same steps as listed above are followed, only the last step is not performed. An ICE curve shows the individual predictions instead of averaging all the predictions (like in a PDP). The function ice to generate an ICE curve is therefore very similar to par_dep:
ice <- function(object, data, grid) {
# Initialize a matrix to save the effect
ice_effect <- matrix(0, nrow = nrow(grid), ncol = nrow(data))
# Iterate over the grid values to calculate the effect
for (i in seq_len(nrow(ice_effect))) {
ice_effect[i, ] <-
data %>%
dplyr::mutate(!! names(grid) := grid[i, ]) %>%
predict_model(object, newdata = .)
}
return(cbind(grid, ice_effect))
}
We will now use this function to generate the ICE effect for the bonus-malus level in frequency models:
# Use a random sample of the training observations
set.seed(54321)
mtpl_trn_sample <- mtpl_trn[sample(seq_len(nrow(mtpl_trn)), size = 1000), ]
# Define the grid for the ages
grid_bm <- data.frame('bm' = 0:22)
# Calculate the ICE effect
ice_tree <- tree_freq %>% ice(data = mtpl_trn_sample,
grid = grid_bm)
ice_gbm <- gbm_freq %>% ice(data = mtpl_trn_sample,
grid = grid_bm)
After some reshaping we plot these ICE curves with the PD effect on top, as in Figure 8 of the paper:
gridExtra::grid.arrange(
ice_tree %>% reshape2::melt(id.vars = 'bm',
value.name = 'ice',
variable.name = 'observation') %>%
dplyr::group_by(bm) %>%
dplyr::mutate(pd = mean(ice)) %>%
ggplot(aes(x = bm)) +
geom_line(aes(y = ice, group = observation), color = 'grey', alpha = 0.1) +
geom_line(aes(y = pd), size = 1, color = 'navy'),
ice_gbm %>% reshape2::melt(id.vars = 'bm',
value.name = 'ice',
variable.name = 'observation') %>%
dplyr::group_by(bm) %>%
dplyr::mutate(pd = mean(ice)) %>%
ggplot(aes(x = bm)) +
geom_line(aes(y = ice, group = observation), color = 'grey', alpha = 0.1) +
geom_line(aes(y = pd), size = 1, color = 'navy'),
ncol = 2
)

Interaction effects
Tree-based models are often praised for their ability to detect interaction effects between variables. Friedman’s H-statistic estimates the interaction strength by measuring how much of the prediction variance originates from the interaction, see Section 4.4 in the paper for the details. The function interact.gbm calculates the H-statistic for a gbm object. (Note: the function interact.gbm is not exported in Harry Southworth’s version, so I include the function in the Rmd source of this Notebook.)
We now calculate two-way interaction strengths between variables and verify some values in Table 4 of the paper:
gbm_freq %>% interact.gbm(data = mtpl_trn,
i.var = c('fuel', 'power')) %>% round(4)
[1] 0.1666
gbm_freq %>% interact.gbm(data = mtpl_trn,
i.var = c('ageph', 'sex')) %>% round(4)
[1] 0.1293
gbm_freq %>% interact.gbm(data = mtpl_trn,
i.var = c('agec', 'coverage')) %>% round(4)
[1] 0.1185
gbm_freq %>% interact.gbm(data = mtpl_trn,
i.var = c('ageph', 'power')) %>% round(4)
[1] 0.1062
We use the partial dependence effects of a variable, grouped by another variable, to get an insight on the interaction behavior between those two variables. The function par_dep_by allows to generate such grouped PD effects:
par_dep_by <- function(object, data, grid, by_var, ngroups = NULL) {
# Initialize a matrix to save the effect
ice_effect <- matrix(0, nrow = nrow(data), ncol = nrow(grid))
# Iterate over the grid values to calculate the effect
for (i in seq_len(ncol(ice_effect))) {
ice_effect[, i] <-
data %>%
dplyr::mutate(!! names(grid) := grid[i, ]) %>%
predict_model(object, newdata = .)
}
# Add the grouping variable to the effect
pd_gr <- data %>% dplyr::select(!! by_var) %>%
cbind(ice_effect)
# Bin the grouping variable in groups
if (!is.null(ngroups)) {
bins <- data %>% dplyr::pull(!! by_var) %>%
cut(breaks = unique(quantile(., probs = seq(0, 1, 1/ngroups))),
include.lowest = TRUE, dig.lab = 4)
pd_gr <- pd_gr %>% dplyr::mutate(!! by_var := bins)
}
# Calculate the PD effect for each group
pd_gr <- pd_gr %>% dplyr::group_by_at(by_var) %>%
dplyr::summarise_all(mean) %>%
dplyr::rename(setNames(as.character(seq_len(nrow(grid))), unlist(grid)))
# Center the PD effects to start from zero
pd_gr[2:ncol(pd_gr)] <- pd_gr[2:ncol(pd_gr)] - pd_gr[[2]]
return(pd_gr)
}
We calculate the PD effect for power, grouped by ageph and fuel. Note that we use ngroups = 5 for the continuous variable ageph but ngroups = NULL for the factor variable fuel (one group for each factor level).
pd_power_ageph <- gbm_freq %>% par_dep_by(data = mtpl_trn_sample,
grid = data.frame('power' = 30:150),
by_var = 'ageph',
ngroups = 5)
pd_power_fuel <- gbm_freq %>% par_dep_by(data = mtpl_trn_sample,
grid = data.frame('power' = 30:150),
by_var = 'fuel',
ngroups = NULL)
We visualize the interaction behavior between power and both ageph and fuel. More graphs are available in Figure 9 in the paper. (Note: the bin labels for ageph differ because a smaller sample size is used in this Notebook.)
gridExtra::grid.arrange(
pd_power_ageph %>% reshape2::melt(id.var = 'ageph',
value.name = 'pd_group',
variable.name = 'power') %>%
dplyr::mutate(power = as.numeric(as.character(power))) %>%
ggplot(aes(x = power, y = pd_group)) +
geom_line(aes(group = ageph, colour = ageph)),
pd_power_fuel %>% reshape2::melt(id.var = 'fuel',
value.name = 'pd_group',
variable.name = 'power') %>%
dplyr::mutate(power = as.numeric(as.character(power))) %>%
ggplot(aes(x = power, y = pd_group)) +
geom_line(aes(group = fuel, colour = fuel)),
ncol = 2
)

Statistical performance
After gaining some insights from the different ML models, we now put focus on comparing the out-of-sample performance on the test data mtpl_tst. We predict each ML model on the test data:
oos_pred <- tibble::tibble(
tree_freq = tree_freq %>% predict_model(newdata = mtpl_tst),
rf_freq = rf_freq %>% predict_model(newdata = mtpl_tst),
gbm_freq = gbm_freq %>% predict_model(newdata = mtpl_tst),
tree_sev = tree_sev %>% predict_model(newdata = mtpl_tst),
rf_sev = rf_sev %>% predict_model(newdata = mtpl_tst),
gbm_sev = gbm_sev %>% predict_model(newdata = mtpl_tst)
)
These predictions are compared to the observed values in mtpl_tst with the Poisson/gamma deviance for frequency/severity models respectively:
# Poisson deviance
dev_poiss <- function(ytrue, yhat) {
-2 * mean(dpois(ytrue, yhat, log = TRUE) - dpois(ytrue, ytrue, log = TRUE), na.rm = TRUE)
}
# Gamma deviance
dev_gamma <- function(ytrue, yhat, wcase) {
-2 * mean(wcase * (log(ytrue/yhat) - (ytrue - yhat)/yhat), na.rm = TRUE)
}
The out-of-sample deviances are calculated below. These are the values for data fold 3 in Figure 10 of the paper.
# Calculate the Poisson deviance for the frequency models
oos_pred %>% dplyr::select(ends_with('_freq')) %>%
purrr::map(~ dev_poiss(mtpl_tst$nclaims, .x * mtpl_tst$expo))
$tree_freq
[1] 0.5377917
$rf_freq
[1] 0.5345945
$gbm_freq
[1] 0.5307879
# Calculate the gamma deviance for the severity models
oos_pred %>% dplyr::select(ends_with('_sev')) %>%
purrr::map(~ dev_gamma(mtpl_tst$average, .x, mtpl_tst$nclaims))
$tree_sev
[1] 2.296448
$rf_sev
[1] 2.279431
$gbm_sev
[1] 2.284528
Economic lift
After comparing the ML models for frequency and severity, we now turn to a comparison at the premium level. We calculate the predicted premiums for the test data mtpl_tst by multiplying the frequency and severity:
oos_pred <- oos_pred %>% dplyr::mutate(
tree_prem = tree_freq * tree_sev,
rf_prem = rf_freq * rf_sev,
gbm_prem = gbm_freq * gbm_sev
)
The predicted premium totals for each model (adjusted for exposure) are calculated below. These correspond to the values in Table 5 of the paper for data fold 3. (Note: the values for the random forest are slighlty different due to an implementation update to the distRforest package regarding subsampling.)
oos_pred %>% dplyr::select(ends_with('_prem')) %>%
dplyr::summarise_all(~ sum(.x * mtpl_tst$expo))
We now focus on some model lift measures, which are introduced and analyzed in Sections 5.1 and 5.2 of the paper. To streamline the coding we add the observed target values from the test data mtpl_tst to the predictions data:
oos_pred <- oos_pred %>% dplyr::mutate(
nclaims = mtpl_tst$nclaims,
expo = mtpl_tst$expo,
amount = mtpl_tst$amount
)
Loss ratio lift
The loss ratio lift is assessed by applying the following steps:
- sort policies from smallest to largest relativity
- bin the policies in groups of equal exposure
- calculate the loss ratio in each bin using the benchmark premium
loss_ratio_lift <- function(data, bench, comp, ngroups) {
# Calculate relativity and sort from small to large
data %>% dplyr::mutate(r = get(paste0(comp, '_prem')) / get(paste0(bench, '_prem'))) %>%
dplyr::arrange(r) %>%
# Bin in groups of equal exposure
dplyr::mutate(bin = cut(cumsum(expo),
breaks = sum(expo) * (0:ngroups) / ngroups,
labels = FALSE)) %>%
dplyr::group_by(bin) %>%
dplyr::mutate(r_lab = paste0('[', round(min(r), 2), ',', round(max(r), 2), ']')) %>%
# Calculate loss ratio per bin
dplyr::summarise(r_lab = r_lab[1],
loss_ratio = sum(amount) / sum(get(paste0(bench, '_prem'))),
sum_expo = sum(expo))
}
We calculate the loss ratio lifts for the tree as benchmark and GBM as competitor and vice versa:
lrl_gbm_tree <- oos_pred %>% loss_ratio_lift(bench = 'tree',
comp = 'gbm',
ngroups = 5)
lrl_gbm_tree
lrl_tree_gbm <- oos_pred %>% loss_ratio_lift(bench = 'gbm',
comp = 'tree',
ngroups = 5)
lrl_tree_gbm
Plotting these results next to each other clearly shows that the GBM aligns the risk better compared to the tree:
gridExtra::grid.arrange(
lrl_gbm_tree %>% ggplot(aes(x = r_lab, y = loss_ratio)) +
geom_bar(stat = 'identity') +
ggtitle('comp: gbm / bench: tree'),
lrl_tree_gbm %>% ggplot(aes(x = r_lab, y = loss_ratio)) +
geom_bar(stat = 'identity') +
ggtitle('comp: tree / bench: gbm'),
ncol = 2
)

Double lift
The double lift is assessed by applying the following steps:
- sort policies from smallest to largest relativity
- bin the policies in groups of equal exposure
- calculate the average loss amount and average premiums (comp & bench) in each bin
- calculate the percentage error of premium (comp & bench) to loss in each bin
double_lift <- function(data, bench, comp, ngroups) {
# Calculate relativity and sort from small to large
data %>% dplyr::mutate(r = get(paste0(comp, '_prem')) / get(paste0(bench, '_prem'))) %>%
dplyr::arrange(r) %>%
# Bin in groups of equal exposure
dplyr::mutate(bin = cut(cumsum(expo),
breaks = sum(expo) * (0:ngroups) / ngroups,
labels = FALSE)) %>%
dplyr::group_by(bin) %>%
dplyr::mutate(r_lab = paste0('[', round(min(r), 2), ',', round(max(r), 2), ']')) %>%
# Calculate percentage errors for both tariffs
dplyr::summarise(r_lab = r_lab[1],
error_comp = mean(get(paste0(comp, '_prem'))) / mean(amount) - 1,
error_bench = mean(get(paste0(bench, '_prem'))) / mean(amount) - 1,
sum_expo = sum(expo))
}
We calculate the double lift for the tree as benchmark and GBM as competitor:
dbl_gbm_tree <- oos_pred %>% double_lift(bench = 'tree',
comp = 'gbm',
ngroups = 5)
dbl_gbm_tree
Plotting these results clearly shows that the GBM aligns the risk better compared to the tree:
dbl_gbm_tree %>% reshape2::melt(id.vars = c('r_lab', 'bin' , 'sum_expo'),
value.name = 'perc_err',
variable.name = 'tariff') %>%
ggplot(aes(x = r_lab, y = perc_err)) +
geom_line(aes(group = tariff, colour = tariff))

Gini index
The last measure for economic lift that we analyze is the Gini index obtained from an ordered Lorenz curve. The function gini() from the cplm package allows to calculate Gini indices for competing models. The mini-max strategy selects the GBM as the model that is least vulnerable to alternative models:
library(cplm)
gini(loss = 'amount',
score = paste0(c('tree', 'rf', 'gbm'), '_prem'),
data = as.data.frame(oos_pred))
Call:
gini(loss = "amount", score = paste0(c("tree", "rf", "gbm"),
"_prem"), data = as.data.frame(oos_pred))
Gini indices:
tree_prem rf_prem gbm_prem
tree_prem 0.0000 11.6812 12.8464
rf_prem -2.0140 0.0000 6.6561
gbm_prem -0.2989 3.3186 0.0000
Standard errors:
tree_prem rf_prem gbm_prem
tree_prem 0.000 2.434 2.467
rf_prem 2.454 0.000 2.601
gbm_prem 2.469 2.598 0.000
The selected score is gbm_prem.
We can program the mini-max strategy explicitly as follows, which gives the ranking gbm > rf > tree:
gini(loss = 'amount',
score = paste0(c('tree', 'rf', 'gbm'), '_prem'),
data = as.data.frame(oos_pred)) %>%
slot('gini') %>%
as.data.frame() %>%
dplyr::mutate(max_gini = pmax(tree_prem, rf_prem, gbm_prem)) %>%
dplyr::mutate(bench = c('tree', 'rf', 'gbm')) %>%
dplyr::arrange(max_gini)
tree_prem rf_prem gbm_prem max_gini bench
1 -0.2989198 3.318631 0.000000 3.318631 gbm
2 -2.0140090 0.000000 6.656142 6.656142 rf
3 0.0000000 11.681220 12.846444 12.846444 tree
Note: the values differ from those in Table 6 of the paper because the analysis in this Notebook uses only the out-of-sample observations from \(\mathcal{D}_3\), while the results in the paper use the out-of-sample data from all six folds \(\mathcal{D}_1\) up to \(\mathcal{D}_6\). The conclusions remain the same however.
Conclusions
This Notebook replicates most of the results from our paper on “Boosting insights in insurance tariff plans with tree-based machine learning methods”. Hopefully this helps you to jumpstart your tree-based ML analysis for insurance pricing or related applications. Happy coding!
LS0tCnRpdGxlOiAnVHJlZS1iYXNlZCBNTCBmb3IgaW5zdXJhbmNlIHByaWNpbmcnCmF1dGhvcjogJ1JvZWwgSGVuY2thZXJ0cywgTWFyaWUtUGllciBDw7R0w6ksIEthdHJpZW4gQW50b25pbyBhbmQgUm9lbCBWZXJiZWxlbicKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZyA6IG5vbmUKICAgIHRoZW1lIDogY29zbW8KLS0tCgpgYGB7Y3NzLCBlY2hvID0gRkFMU0V9CnByZSBjb2RlLCBwcmUsIGNvZGUgewogIHdoaXRlLXNwYWNlOiBwcmUgIWltcG9ydGFudDsKICBvdmVyZmxvdy14OiBzY3JvbGwgIWltcG9ydGFudDsKICB3b3JkLWJyZWFrOiBrZWVwLWFsbCAhaW1wb3J0YW50OwogIHdvcmQtd3JhcDogaW5pdGlhbCAhaW1wb3J0YW50Owp9CmBgYAoKPHN0eWxlPgpib2R5IHsgdGV4dC1hbGlnbjoganVzdGlmeX0KPC9zdHlsZT4KClRoaXMgUiBNYXJrZG93biBOb3RlYm9vayBpbGx1c3RyYXRlcyB0aGUgdGVjaG5pcXVlcyB1c2VkIGluIHRoZSBwYXBlciAiQm9vc3RpbmcgaW5zaWdodHMgaW4gaW5zdXJhbmNlIHRhcmlmZiBwbGFucyB3aXRoIHRyZWUtYmFzZWQgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIiB2aWEgY29kZSBleGFtcGxlcy4gVGhlIGZ1bGwgcGFwZXIgaXMgYXZhaWxhYmxlIG9uIFthclhpdl0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE5MDQuMTA4OTApIGFuZCBpcyBwdWJsaXNoZWQgaW4gdGhlIFtOb3J0aCBBbWVyaWNhbiBBY3R1YXJpYWwgSm91cm5hbF0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL3RvYy91YWFqMjAvY3VycmVudCkuCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKZ2dwbG90Mjo6dGhlbWVfc2V0KHRoZW1lX2J3KCkpCmdncGxvdDI6OnRoZW1lX3VwZGF0ZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpCmBgYAoKCiMjIE1UUEwgcG9ydGZvbGlvCgpXZSBzdHVkeSBhIG1vdG9yIHRoaXJkIHBhcnR5IGxpYWJpbGl0eSAoTVRQTCkgcG9ydGZvbGlvLiBEZXRhaWxzIG9uIHRoZSBkYXRhIGFyZSBhdmFpbGFibGUgaW4gU2VjdGlvbiA0LjEgb2YgdGhlIHBhcGVyLgoKYGBge3IgZGF0YV9yZWFkfQptdHBsX2RhdGEgPC0gcmVhZFJEUygnbXRwbF9kYXRhLnJkcycpCnN0cihtdHBsX2RhdGEpCmBgYAoKVGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXJlIHVzZWQgYXMgcHJlZGljdG9yIGZlYXR1cmVzLCBzZWUgVGFibGUgNyBpbiBBcHBlbmRpeCBBIG9mIHRoZSBwYXBlciBmb3IgYSBkZXNjcmlwdGlvbi4KCmBgYHtyIGRhdGFfdmFyc30KZmVhdHVyZXMgPC0gYygnY292ZXJhZ2UnLCAnZnVlbCcsICdzZXgnLCAndXNlJywgJ2ZsZWV0JywgCiAgICAgICAgICAgICAgJ2FnZXBoJywgJ3Bvd2VyJywgJ2FnZWMnLCAnYm0nLAogICAgICAgICAgICAgICdsb25nJywgJ2xhdCcpCmBgYAoKCiMjIFRyYWluIGFuZCB0ZXN0IHNwbGl0CgpXZSBmb2xsb3cgdGhlIHNhbWUgZGF0YSBwYXJ0aXRpb24gYXBwcm9hY2ggYXMgZGlzY3Vzc2VkIGluIHRoZSAiQ3Jvc3MtdmFsaWRhdGlvbiIgcGFyYWdyYXBoIG9mIFNlY3Rpb24gMy4zIGluIHRoZSBwYXBlci4gSXQgcGFydGl0aW9ucyB0aGUgZGF0YSBpbiBzaXggc3Vic2V0cyBhbmQgYWxsb3dzIHVzIHRvIHJlcGxpY2F0ZSB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIFRhYmxlIDIgYXMgZm9sbG93czoKCmBgYHtyIGRhdGFfZm9sZH0KIyBBZGQgdGhlIGZvbGQgaW5kaWNhdG9yCm10cGxfZGF0YSA8LSBtdHBsX2RhdGEgJT4lIGRwbHlyOjphcnJhbmdlKG5jbGFpbXMsIGFtb3VudCwgZXhwbykgJT4lIAogIGRwbHlyOjptdXRhdGUoZm9sZCA9IHBhc3RlMCgnZGF0YScsIHJlcChzZXFfbGVuKDYpLCBsZW5ndGggPSBucm93KG10cGxfZGF0YSkpKSkKIyBDYWxjdWxhdGUgc3VtbWFyeSBzdGF0aXN0aWNzCm10cGxfZGF0YSAlPiUgZHBseXI6Omdyb3VwX2J5KGZvbGQpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKHN1bShuY2xhaW1zKSAvIHN1bShleHBvKSwKICAgICAgICAgICAgICAgICAgIHN1bShhbW91bnQpIC8gc3VtKG5jbGFpbXMpKQpgYGAKCkluIHRoaXMgTm90ZWJvb2sgd2UgYXNzaWduICRcbWF0aGNhbHtEfV8zJCBhcyB0aGUgdGVzdCBzZXQgYW5kIHRoZSBjb2xsZWN0aW9uICRceyBcbWF0aGNhbHtEfV8xLCBcbWF0aGNhbHtEfV8yLCBcbWF0aGNhbHtEfV80LCBcbWF0aGNhbHtEfV81LCBcbWF0aGNhbHtEfV82IFx9JCBhcyB0aGUgdHJhaW5pbmcgc2V0LiBUaGlzIG1hdGNoZXMgdGhlIHNldHVwIG9mICJEYXRhIGZvbGQgMyIgaW4gRmlndXJlIDMgb2YgdGhlIHBhcGVyIHdpdGggdGhlIHRyYWluaW5nIGRhdGEgaW4gYmx1ZSBhbmQgdGhlIHRlc3QgZGF0YSBpbiByZWQuCgpgYGB7ciBkYXRhX3NwbGl0fQojIFN1YnNldCB0aGUgdHJhaW5pbmcgZGF0YQptdHBsX3RybiA8LSBtdHBsX2RhdGEgJT4lIGRwbHlyOjpmaWx0ZXIoZm9sZCAhPSAnZGF0YTMnKQojIFN1YnNldCB0aGUgdGVzdCBkYXRhCm10cGxfdHN0IDwtIG10cGxfZGF0YSAlPiUgZHBseXI6OmZpbHRlcihmb2xkID09ICdkYXRhMycpCmBgYAoKVGhpcyByZXN1bHRzIGluIGByIG5yb3cobXRwbF90cm4pYC9gciBucm93KG10cGxfdHN0KWAgb2JzZXJ2YXRpb25zIGluIHRoZSB0cmFpbi90ZXN0IGRhdGEgcmVzcGVjdGl2ZWx5LiBJbiB0aGUgZm9sbG93aW5nIG1vZGVsaW5nIHNlY3Rpb25zLCB3ZSB1c2UgdGhlIGh5cGVyLXBhcmFtZXRlciBzZXR0aW5ncyBmcm9tIFRhYmxlIDEgYW5kIHRoZSBvcHRpbWFsIHR1bmluZyBwYXJhbWV0ZXIgc2V0dGluZ3MgZnJvbSBUYWJsZSAzIGZvciBkYXRhIGZvbGQgMy4KCiMjIEZyZXF1ZW5jeSBtb2RlbGluZwoKV2Ugc3RhcnQgYnkgbW9kZWxpbmcgY2xhaW0gZnJlcXVlbmN5LCBpLmUuLCB0aGUgbnVtYmVyIG9mIGNsYWltcyBhIHBvbGljeWhvbGRlciBpcyBleHBlY3RlZCB0byBmaWxlIHdpdGggdGhlIGluc3VyZXIuIEl0IGlzIGltcG9ydGFudCB0byBhY2NvdW50IGZvciB0aGUgcGVyaW9kIG9mIGV4cG9zdXJlLXRvLXJpc2ssIGkuZS4sIHRoZSBmcmFjdGlvbiBvZiB0aGUgeWVhciB0aGF0IGEgcG9saWN5aG9sZGVyIHdhcyBjb3ZlcmVkIGJ5IHRoZSBwb2xpY3kgYW5kIHRoZXJlZm9yZSBleHBvc2VkIHRvIHRoZSByaXNrIG9mIGZpbGluZyBhIGNsYWltLiBUaGUgbnVtYmVyIG9mIGNsYWltcyBhbmQgZXhwb3N1cmUgcGVyaW9kIGFyZSBhdmFpbGFibGUgaW4gYG10cGxfZGF0YWAgYXMgdGhlIHZhcmlhYmxlcyBgbmNsYWltc2AgYW5kIGBleHBvYCByZXNwZWN0aXZlbHk6CgpgYGB7ciBmcmVxX2RhdGEsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKAogIGdncGxvdChtdHBsX2RhdGEsIGFlcyh4ID0gbmNsYWltcykpICsgCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgY29sID0gJ2JsYWNrJykgKyAKICAgIGxhYnMoeSA9ICdQcm9wb3J0aW9uJykgKyBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSwKICBnZ3Bsb3QobXRwbF9kYXRhLCBhZXMoeCA9IGV4cG8pKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAwLjA1LCBjb2wgPSAnYmxhY2snKSArCiAgICBsYWJzKHkgPSAnUHJvcG9ydGlvbicpICsgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCksCiAgbmNvbCA9IDIKKQpgYGAKCgojIyMgUmVncmVzc2lvbiB0cmVlCgpXZSBmaXQgYSBQb2lzc29uIHJlZ3Jlc3Npb24gdHJlZSBmb3IgY2xhaW0gZnJlcXVlbmN5IHdpdGggdGhlIGBycGFydCgpYCBmdW5jdGlvbiBhbmQgYG1ldGhvZCA9ICdwb2lzc29uJ2AgZnJvbSB0aGUgYGRpc3RSZm9yZXN0YCBwYWNrYWdlIChhdmFpbGFibGUgZnJvbSBSb2VsIEhlbmNrYWVydHMncyBbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vaGVuY2tyL2Rpc3RSZm9yZXN0KSk6CgpgYGB7ciBmcmVxX3RyZWV9CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdoZW5ja3IvZGlzdFJmb3Jlc3QnKQpsaWJyYXJ5KGRpc3RSZm9yZXN0KQp0cmVlX2ZyZXEgPC0gZGlzdFJmb3Jlc3Q6OnJwYXJ0KAogIGZvcm11bGEgPSBhcy5mb3JtdWxhKHBhc3RlKCdjYmluZChleHBvLCBuY2xhaW1zKSB+JywgcGFzdGUoZmVhdHVyZXMsIGNvbGxhcHNlID0gJyArICcpKSksCiAgZGF0YSA9IG10cGxfdHJuLAogIG1ldGhvZCA9ICdwb2lzc29uJywKICBwYXJtcyA9IGxpc3Qoc2hyaW5rID0gMC4xMjUpLCAjIGdhbW1hIGluIFRhYmxlIDMKICBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDEuMWUtNCwgIyBjcCBpbiBUYWJsZSAzCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWluYnVja2V0ID0gMC4wMSAqIG5yb3cobXRwbF90cm4pLCAjIGthcHBhIGluIFRhYmxlIDEKICAgICAgICAgICAgICAgICAgICAgICAgICB4dmFsID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhjb21wZXRlID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhzdXJyb2dhdGUgPSAwKQogICkKYGBgCgoqTm90ZTogW2BkaXN0UmZvcmVzdGBdKGh0dHBzOi8vaGVuY2tyLmdpdGh1Yi5pby9kaXN0UmZvcmVzdC8pIGlzIGFuIGV4dGVuc2lvbiBvZiBgcnBhcnRgIGFuZCBhbGxvd3MgZm9yIHNldmVyaXR5IGRpc3RyaWJ1dGlvbnMgYW5kIHJhbmRvbSBmb3Jlc3QgZ2VuZXJhdGlvbi4qCgojIyMgUmFuZG9tIGZvcmVzdAoKV2UgZml0IGEgUG9pc3NvbiByYW5kb20gZm9yZXN0IGZvciBjbGFpbSBmcmVxdWVuY3kgd2l0aCB0aGUgYHJmb3Jlc3QoKWAgZnVuY3Rpb24gYW5kIGBtZXRob2QgPSAncG9pc3NvbidgIGZyb20gdGhlIGBkaXN0UmZvcmVzdGAgcGFja2FnZSAoYXZhaWxhYmxlIGZyb20gUm9lbCBIZW5ja2FlcnRzJ3MgW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2hlbmNrci9kaXN0UmZvcmVzdCkpOgoKYGBge3IgZnJlcV9yZn0Kc2V0LnNlZWQoNTQzMjEpCnJmX2ZyZXEgPC0gZGlzdFJmb3Jlc3Q6OnJmb3Jlc3QoCiAgZm9ybXVsYSA9IGFzLmZvcm11bGEocGFzdGUoJ2NiaW5kKGV4cG8sIG5jbGFpbXMpIH4nLCBwYXN0ZShmZWF0dXJlcywgY29sbGFwc2UgPSAnICsgJykpKSwKICBkYXRhID0gbXRwbF90cm4sCiAgbWV0aG9kID0gJ3BvaXNzb24nLAogIG50cmVlcyA9IDQwMCwgIyBUIGluIFRhYmxlIDMKICBuY2FuZCA9IDgsICMgbSBpbiBUYWJsZSAzCiAgc3Vic2FtcGxlID0gMC43NSwgIyBkZWx0YSBpbiBUYWJsZSAxCiAgcGFybXMgPSBsaXN0KHNocmluayA9IDAuMjUpLCAjIGdhbW1hIGluIFRhYmxlIDEKICBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDAsICMgY3AgaW4gVGFibGUgMQogICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmJ1Y2tldCA9IDAuMDEgKiAwLjc1ICogbnJvdyhtdHBsX3RybiksICMga2FwcGEgKiBkZWx0YSBpbiBUYWJsZSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgeHZhbCA9IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4Y29tcGV0ZSA9IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4c3Vycm9nYXRlID0gMCksCiAgcmVkX21lbSA9IFRSVUUgIyByZWR1Y2VzIHRoZSBtZW1vcnkgZm9vdHByaW50IG9mIGluZGl2aWR1YWwgcnBhcnQgdHJlZXMKICApCmBgYAoKIyMjIEdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUKCldlIGZpdCBhIFBvaXNzb24gR0JNIGZvciBjbGFpbSBmcmVxdWVuY3kgd2l0aCB0aGUgYGdibSgpYCBmdW5jdGlvbiBhbmQgYGRpc3RyaWJ1dGlvbiA9ICdwb2lzc29uJ2AgZnJvbSB0aGUgYGdibWAgcGFja2FnZSAoYXZhaWxhYmxlIGZyb20gSGFycnkgU291dGh3b3J0aCdzIFtHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXJyeXNvdXRod29ydGgvZ2JtKSk6CgpgYGB7ciBmcmVxX2dibSwgbWVzc2FnZSA9IEZBTFNFfQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignaGFycnlzb3V0aHdvcnRoL2dibScpCmxpYnJhcnkoZ2JtKQpzZXQuc2VlZCg1NDMyMSkKZ2JtX2ZyZXEgPC0gZ2JtKAogIGZvcm11bGEgPSBhcy5mb3JtdWxhKHBhc3RlKCduY2xhaW1zIH4gb2Zmc2V0KGxvZyhleHBvKSkgKycsIHBhc3RlKGZlYXR1cmVzLCBjb2xsYXBzZSA9ICcgKyAnKSkpLAogIGRhdGEgPSBtdHBsX3RybiwKICBkaXN0cmlidXRpb24gPSAncG9pc3NvbicsCiAgbi50cmVlcyA9IDE0MDAsICMgVCBpbiBUYWJsZSAzCiAgaW50ZXJhY3Rpb24uZGVwdGggPSA1LCAjIGQgaW4gVGFibGUgMwogIHNocmlua2FnZSA9IDAuMDEsICMgbGFtYmRhIGluIFRhYmxlIDEKICBiYWcuZnJhY3Rpb24gPSAwLjc1LCAjIGRlbHRhIGluIFRhYmxlIDEKICBuLm1pbm9ic2lubm9kZSA9IDAuMDEgKiAwLjc1ICogbnJvdyhtdHBsX3RybiksICMga2FwcGEgKiBkZWx0YSBpbiBUYWJsZSAxCiAgdmVyYm9zZSA9IEZBTFNFCiAgKQpgYGAKCipOb3RlOiBVbmxpa2UgdGhlIENSQU4gdmVyc2lvbiBvZiBgZ2JtYCwgSFMncyBpbXBsZW1lbnRhdGlvbiBhbGxvd3MgZm9yIGEgZ2FtbWEgZGlzdHJpYnV0aW9uIGZvciBjbGFpbSBzZXZlcml0eS4qCgojIyBTZXZlcml0eSBtb2RlbGluZwoKTmV4dCwgd2UgYWxzbyBtb2RlbCBjbGFpbSBzZXZlcml0eSwgaS5lLiwgdGhlIGV4cGVjdGVkIGxvc3MgYW1vdW50IGZvciBhIGNsYWltIGlmIGl0IGdldHMgZmlsZWQuIFRoZSB2YXJpYWJsZSBgYW1vdW50YCBpbiBgbXRwbF9kYXRhYCBjb250YWlucyB0aGUgdG90YWwgbG9zcyBhbW91bnQgb3ZlciBhbGwgY2xhaW1zIGZpbGVkIGJ5IGEgcG9saWN5aG9sZGVyLiBJbiBvcmRlciB0byBnZXQgYW4gZXN0aW1hdGUgb2YgdGhlIGluZGl2aWR1YWwgY2xhaW0gc2V2ZXJpdHksIHRoZSB2YXJpYWJsZSBgYXZlcmFnZWAgY29udGFpbnMgdGhlIHRvdGFsIGxvc3MgYW1vdW50IGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiBjbGFpbXMuIE9ubHkgdGhlIG9ic2VydmF0aW9ucyBjb250YWluaW5nIGEgY2xhaW0gY2FuIGJlIHVzZWQgdG8gbW9kZWwgY2xhaW0gc2V2ZXJpdHk6CgpgYGB7ciBzZXZfZGF0YSwgZmlnLndpZHRoPTgsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmhlaWdodD00fQojIE9ubHkgcmV0YWluIHRoZSBjbGFpbXMKbXRwbF90cm5fY2xhaW1zIDwtIG10cGxfdHJuICU+JSBkcGx5cjo6ZmlsdGVyKG5jbGFpbXMgPiAwKQojIFBsb3QgdGhlIGRlbnNpdHkgb2YgYWxsIG9ic2VydmF0aW9ucyBhbmQgdGhvc2UgYmVsb3cgMTAgMDAwIEV1cm8KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoCiAgZ2dwbG90KG10cGxfdHJuX2NsYWltcywgYWVzKHggPSBhdmVyYWdlKSkgKyAKICAgIGdlb21fZGVuc2l0eShhZGp1c3QgPSAzLCBjb2wgPSAnYmxhY2snLCBmaWxsID0gJ2dyYXknKSArCiAgICBsYWJzKHkgPSAnRGVuc2l0eScpLAogIGdncGxvdChtdHBsX3Rybl9jbGFpbXMsIGFlcyh4ID0gYXZlcmFnZSkpICsgCiAgICBnZW9tX2RlbnNpdHkoYWRqdXN0ID0gMywgY29sID0gJ2JsYWNrJywgZmlsbCA9ICdncmF5JykgKwogICAgbGFicyh5ID0gJ0RlbnNpdHknKSArIHhsaW0oMCwgMWU0KSwKICBuY29sID0gMgogICkKYGBgCgojIyMgUmVncmVzc2lvbiB0cmVlCgpXZSBmaXQgYSBnYW1tYSByZWdyZXNzaW9uIHRyZWUgZm9yIGNsYWltIHNldmVyaXR5IHdpdGggdGhlIGBycGFydCgpYCBmdW5jdGlvbiBhbmQgYG1ldGhvZCA9ICdnYW1tYSdgIGZyb20gdGhlIGBkaXN0UmZvcmVzdGAgcGFja2FnZSAoYXZhaWxhYmxlIGZyb20gUm9lbCBIZW5ja2FlcnRzJ3MgW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2hlbmNrci9kaXN0UmZvcmVzdCkpOgoKYGBge3Igc2V2X3RyZWV9CnRyZWVfc2V2IDwtIGRpc3RSZm9yZXN0OjpycGFydCgKICBmb3JtdWxhID0gYXMuZm9ybXVsYShwYXN0ZSgnYXZlcmFnZSB+JywgcGFzdGUoZmVhdHVyZXMsIGNvbGxhcHNlID0gJyArICcpKSksCiAgZGF0YSA9IG10cGxfdHJuX2NsYWltcywKICB3ZWlnaHRzID0gbmNsYWltcywKICBtZXRob2QgPSAnZ2FtbWEnLAogIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMy43ZS0zLCAjIGNwIGluIFRhYmxlIDMKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSAwLjAxICogbnJvdyhtdHBsX3Rybl9jbGFpbXMpLCAjIGthcHBhIGluIFRhYmxlIDEKICAgICAgICAgICAgICAgICAgICAgICAgICB4dmFsID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhjb21wZXRlID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhzdXJyb2dhdGUgPSAwKQogICkKYGBgCgojIyMgUmFuZG9tIGZvcmVzdAoKV2UgZml0IGEgZ2FtbWEgcmFuZG9tIGZvcmVzdCBmb3IgY2xhaW0gc2V2ZXJpdHkgd2l0aCB0aGUgYHJmb3Jlc3QoKWAgZnVuY3Rpb24gYW5kIGBtZXRob2QgPSAnZ2FtbWEnYCBmcm9tIHRoZSBgZGlzdFJmb3Jlc3RgIHBhY2thZ2UgKGF2YWlsYWJsZSBmcm9tIFJvZWwgSGVuY2thZXJ0cydzIFtHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9oZW5ja3IvZGlzdFJmb3Jlc3QpKToKCmBgYHtyIHNldl9yZn0Kc2V0LnNlZWQoNTQzMjEpCnJmX3NldiA8LSBkaXN0UmZvcmVzdDo6cmZvcmVzdCgKICBmb3JtdWxhID0gYXMuZm9ybXVsYShwYXN0ZSgnYXZlcmFnZSB+JywgcGFzdGUoZmVhdHVyZXMsIGNvbGxhcHNlID0gJyArICcpKSksCiAgZGF0YSA9IG10cGxfdHJuX2NsYWltcywKICB3ZWlnaHRzID0gbmNsYWltcywKICBtZXRob2QgPSAnZ2FtbWEnLAogIG50cmVlcyA9IDYwMCwgIyBUIGluIFRhYmxlIDMKICBuY2FuZCA9IDEsICMgbSBpbiBUYWJsZSAzCiAgc3Vic2FtcGxlID0gMC43NSwgIyBkZWx0YSBpbiBUYWJsZSAxCiAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSAwLCAjIGNwIGluIFRhYmxlIDEKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSAwLjAxICogMC43NSAqIG5yb3cobXRwbF90cm5fY2xhaW1zKSwgIyBrYXBwYSAqIGRlbHRhIGluIFRhYmxlIDEKICAgICAgICAgICAgICAgICAgICAgICAgICB4dmFsID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhjb21wZXRlID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhzdXJyb2dhdGUgPSAwKSwKICByZWRfbWVtID0gVFJVRSAjIHJlZHVjZXMgdGhlIG1lbW9yeSBmb290cHJpbnQgb2YgaW5kaXZpZHVhbCBycGFydCB0cmVlcwogICkKYGBgCgoKIyMjIEdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUKCldlIGZpdCBhIGdhbW1hIEdCTSBmb3IgY2xhaW0gc2V2ZXJpdHkgd2l0aCB0aGUgYGdibSgpYCBmdW5jdGlvbiBhbmQgYGRpc3RyaWJ1dGlvbiA9ICdnYW1tYSdgIGZyb20gdGhlIGBnYm1gIHBhY2thZ2UgKGF2YWlsYWJsZSBmcm9tIEhhcnJ5IFNvdXRod29ydGgncyBbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vaGFycnlzb3V0aHdvcnRoL2dibSkpOgoKYGBge3Igc2V2X2dibSwgbWVzc2FnZSA9IEZBTFNFfQpzZXQuc2VlZCg1NDMyMSkKZ2JtX3NldiA8LSBnYm0oCiAgZm9ybXVsYSA9IGFzLmZvcm11bGEocGFzdGUoJ2F2ZXJhZ2UgficsIHBhc3RlKGZlYXR1cmVzLCBjb2xsYXBzZSA9ICcgKyAnKSkpLAogIGRhdGEgPSBtdHBsX3Rybl9jbGFpbXMsCiAgd2VpZ2h0cyA9IG5jbGFpbXMsCiAgZGlzdHJpYnV0aW9uID0gJ2dhbW1hJywKICBuLnRyZWVzID0gNTAwLCAjIFQgaW4gVGFibGUgMwogIGludGVyYWN0aW9uLmRlcHRoID0gMSwgIyBkIGluIFRhYmxlIDMKICBzaHJpbmthZ2UgPSAwLjAxLCAjIGxhbWJkYSBpbiBUYWJsZSAxCiAgYmFnLmZyYWN0aW9uID0gMC43NSwgIyBkZWx0YSBpbiBUYWJsZSAxCiAgbi5taW5vYnNpbm5vZGUgPSAwLjAxICogMC43NSAqIG5yb3cobXRwbF90cm5fY2xhaW1zKSwgIyBrYXBwYSAqIGRlbHRhIGluIFRhYmxlIDEKICB2ZXJib3NlID0gRkFMU0UKICApCmBgYAoKIyMgUHJlZGljdGlvbiBmdW5jdGlvbnMKTm93IGFsbCBNTCBtb2RlbHMgYXJlIGZpdCwgd2UgY2FuIHN0YXJ0IGFuYWx5emluZyB0aGVtLiBUbyBzdHJlYW1saW5lIHRoaXMgcHJvY2Vzcywgd2UgZGVmaW5lIGEgcHJlZGljdCBmdW5jdGlvbiBgcHJlZGljdF9tb2RlbGAgdGhhdCBjYW4gYmUgYXBwbGllZCB0byB0aGUgZGlmZmVyZW50IG1vZGVscyBpbiBhIHVuaWZvcm0gd2F5OgoKYGBge3IgcHJlZF9mdW59CiMgR2VuZXJpYyBwcmVkaWN0aW9uIGZ1bmN0aW9uCnByZWRpY3RfbW9kZWwgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhKSBVc2VNZXRob2QoJ3ByZWRpY3RfbW9kZWwnKQojIFByZWRpY3Rpb24gZnVuY3Rpb24gZm9yIGEgcmVncmVzc2lvbiB0cmVlCnByZWRpY3RfbW9kZWwucnBhcnQgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhKSB7CiAgcHJlZGljdChvYmplY3QsIG5ld2RhdGEsIHR5cGUgPSAndmVjdG9yJykKfQojIFByZWRpY3Rpb24gZnVuY3Rpb24gZm9yIGEgcmFuZG9tIGZvcmVzdApwcmVkaWN0X21vZGVsLnJmb3Jlc3QgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhKSB7CiAgcHJlZGljdChvYmplY3QsIG5ld2RhdGEpCn0KIyBQcmVkaWN0aW9uIGZ1bmN0aW9uIGZvciBhIEdCTQpwcmVkaWN0X21vZGVsLmdibSA8LSBmdW5jdGlvbihvYmplY3QsIG5ld2RhdGEpIHsKICBwcmVkaWN0KG9iamVjdCwgbmV3ZGF0YSwgbi50cmVlcyA9IG9iamVjdCRuLnRyZWVzLCB0eXBlID0gJ3Jlc3BvbnNlJykKfQpgYGAKCgojIyBNb2RlbCBpbnRlcnByZXRhdGlvbgpXZSBub3cgZm9jdXMgb24gZXh0cmFjdGluZyBpbnNpZ2h0cyBmcm9tIHRoZSBNTCBtb2RlbHMgd2l0aCB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcywgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIChQRFBzKSBhbmQgaW5kaXZpZHVhbCBjb25kaXRpb25hbCBleHBlY3RhdGlvbnMgKElDRXMpLiBEZXRhaWxzIGFyZSBhdmFpbGFibGUgaW4gU2VjdGlvbnMgMy40IGFuZCA0LjMgb2YgdGhlIHBhcGVyLgoKIyMjIFZhcmlhYmxlIGltcG9ydGFuY2UKQSBmaXJzdCBpbnRlcmVzdGluZyBpbnNpZ2h0IGlzIHRvIGtub3cgd2hpY2ggZmVhdHVyZXMgYXJlIGltcG9ydGFudCBhY2NvcmRpbmcgdG8geW91ciBtb2RlbC4gVGhpcyBpbmZvcm1hdGlvbiBjYW4gYmUgb2J0YWluZWQgaW4gdGhlIGZvbGxvd2luZyB3YXkgZm9yIHRoZSBkaWZmZXJlbnQgbW9kZWxzIGNvbnNpZGVyZWQ6CgorIHJlZ3Jlc3Npb24gdHJlZTogYWNjZXNzIGltcG9ydGFuY2Ugc2NvcmVzIG9mIGFuIGBycGFydGAgb2JqZWN0IHZpYSBgb2JqZWN0JHZhcmlhYmxlLmltcG9ydGFuY2VgCisgcmFuZG9tIGZvcmVzdDogYXBwbHkgdGhlIGZ1bmN0aW9uIGBkaXN0UmZvcmVzdDo6aW1wb3J0YW5jZV9yZm9yZXN0YCB0byB0aGUgYHJmb3Jlc3RgIG9iamVjdAorIGdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmU6IGFwcGx5IHRoZSBmdW5jdGlvbiBgc3VtbWFyeWAgKHdpdGggb3B0aW9uYWwgYXJndW1lbnRzKSB0byB0aGUgYGdibWAgb2JqZWN0CgpUaGVzZSB0aHJlZSBvcHRpb25zIGFyZSBjb2RlZCBpbiB0aGUgZnVuY3Rpb24gYHZhcl9pbXBgIGJlbG93IHRvIGdlbmVyYXRlIGEgdW5pZm9ybSBvdXRwdXQuIFRoZSBmdW5jdGlvbiBgcGxvdF92YXJfaW1wYCB0YWtlcyB0aGUgY2FsY3VsYXRlZCBpbXBvcnRhbmNlIHNjb3JlcyBhbmQgc2hvd3MgdGhlIHJlc3VsdHMgaW4gYSBgZ2dwbG90YCBiYXIgY2hhcnQuCgpgYGB7ciB2YXJfaW1wX2Z1bn0KdmFyX2ltcCA8LSBmdW5jdGlvbihvYmplY3QpIHsKICAjIENhbGN1bGF0ZSBub24tbm9ybWFsaXplZCBpbXBvcnRhbmNlIHNjb3JlcyBiYXNlZCBvbiB0aGUgbW9kZWwgY2xhc3MKICBzd2l0Y2goY2xhc3Mob2JqZWN0KVsxXSwKICAgICAgICAgJ3JwYXJ0JyA9IGRhdGEuZnJhbWUodmFyaWFibGUgPSBuYW1lcyhvYmplY3QkdmFyaWFibGUuaW1wb3J0YW5jZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydGFuY2UgPSBvYmplY3QkdmFyaWFibGUuaW1wb3J0YW5jZSksCiAgICAgICAgICdyZm9yZXN0JyA9IG9iamVjdCAlPiUgZGlzdFJmb3Jlc3Q6OmltcG9ydGFuY2VfcmZvcmVzdCgpICU+JSAKICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QodmFyaWFibGUsIGltcG9ydGFuY2UpLAogICAgICAgICAnZ2JtJyA9IG9iamVjdCAlPiUgc3VtbWFyeShwbG90aXQgPSBGQUxTRSwgbm9ybWFsaXplID0gRkFMU0UpICU+JSAKICAgICAgICAgICAgICAgICAgICAgIHNldE5hbWVzKGMoJ3ZhcmlhYmxlJywgJ2ltcG9ydGFuY2UnKSkKICAgICAgICAgKSAlPiUgCiAgICAjIE5vcm1hbGl6ZSB0aGUgc2NvcmVzIHRvIHN1bSB0byBvbmUKICAgIGRwbHlyOjptdXRhdGUoc2NhbGVfc3VtID0gcm91bmQoaW1wb3J0YW5jZSAvIHN1bShpbXBvcnRhbmNlKSwgZGlnaXRzID0gNCkpCn0KCnBsb3RfdmFyX2ltcCA8LSBmdW5jdGlvbihkYXRhKSB7CiAgZGF0YSAlPiUgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih2YXJpYWJsZSwgc2NhbGVfc3VtKSwgeSA9IHNjYWxlX3N1bSkpICsgCiAgICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyBjb29yZF9mbGlwKCkgKyAKICAgIGxhYnMoeCA9ICcnLCB5ID0gJ2ltcG9ydGFuY2UnKQp9CmBgYAoKVGhlIHZhcmlhYmxlIGltcG9ydGFuY2Ugc2NvcmVzIGZvciB0aGUgdGhyZWUgZnJlcXVlbmN5IE1MIG1vZGVscyBhcmUgc2hvd24gYmVsb3cuIFRoZXNlIGFyZSB0aGUgZ3JlZW4gYmFycyBmb3IgZGF0YSBmb2xkIDMgaW4gdGhlIGxlZnQgcGFuZWxzIG9mIEZpZ3VyZSA1IGluIHRoZSBwYXBlci4KCmBgYHtyIHZhcl9pbXBfZnJlcSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodD00fQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSh0cmVlX2ZyZXEgJT4lIHZhcl9pbXAgJT4lIHBsb3RfdmFyX2ltcCwKICAgICAgICAgICAgICAgICAgICAgICAgcmZfZnJlcSAlPiUgdmFyX2ltcCAlPiUgcGxvdF92YXJfaW1wLAogICAgICAgICAgICAgICAgICAgICAgICBnYm1fZnJlcSAlPiUgdmFyX2ltcCAlPiUgcGxvdF92YXJfaW1wLAogICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gMykKYGBgCgpUaGUgdmFyaWFibGUgaW1wb3J0YW5jZSBzY29yZXMgZm9yIHRoZSB0aHJlZSBzZXZlcml0eSBNTCBtb2RlbHMgYXJlIHNob3duIGJlbG93LiBUaGVzZSBhcmUgdGhlIGdyZWVuIGJhcnMgZm9yIGRhdGEgZm9sZCAzIGluIHRoZSByaWdodCBwYW5lbHMgb2YgRmlndXJlIDUgaW4gdGhlIHBhcGVyLgoKYGBge3IgdmFyX2ltcF9zZXYsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQ9NH0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UodHJlZV9zZXYgJT4lIHZhcl9pbXAgJT4lIHBsb3RfdmFyX2ltcCwKICAgICAgICAgICAgICAgICAgICAgICAgcmZfc2V2ICU+JSB2YXJfaW1wICU+JSBwbG90X3Zhcl9pbXAsCiAgICAgICAgICAgICAgICAgICAgICAgIGdibV9zZXYgJT4lIHZhcl9pbXAgJT4lIHBsb3RfdmFyX2ltcCwKICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDMpCmBgYAoKCiMjIyBQRFBzCldlIHVzZSBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdHMgKFBEUHMpIHRvIGdldCBhbiBpbnNpZ2h0IG9uIHRoZSByZWxhdGlvbiBiZXR3ZWVuIGEgZmVhdHVyZSBhbmQgdGhlIHRhcmdldC4gVGhlIGZ1bmN0aW9uIGBwYXJfZGVwYCBwZXJmb3JtcyB0aGUgZXNzZW50aWFsIHN0ZXBzIHRvIGdlbmVyYXRlIHN1Y2ggYSBQRCBlZmZlY3QuIFRoZSBmb2xsb3dpbmcgc3RlcHMgYXJlIHBlcmZvcm1lZCBmb3IgZWFjaCB2YWx1ZSBpbiBhIHByZWRlZmluZWQgZ3JpZCBvZiB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3Q6CgorIHVzZSB0aGUgb3JpZ2luYWwgdHJhaW5pbmcgZGF0YSAob3IgYSBzdWJzZXQgdG8gc3BlZWR1cCBjYWxjdWxhdGlvbnMpCisgY2hhbmdlIHRoZSB2YWx1ZSBvZiB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QgdG8gdGhlIGN1cnJlbnQgdmFsdWUgaW4gdGhlIGdyaWQgZm9yIGFsbCBvYnNlcnZhdGlvbnMKKyBwcmVkaWN0IHRoZSBtb2RlbCBvbiB0aGlzIGFsdGVyZWQgZGF0YSBzZXQKKyBjYWxjdWxhdGUgdGhlIG1lYW4gb2YgYWxsIHRoZXNlIHByZWRpY3Rpb25zIHRvIGdldCB0aGUgUEQgZWZmZWN0IGZvciB0aGUgY3VycmVudCBncmlkIHZhbHVlCgoKYGBge3IgcGRwX2Z1bn0KcGFyX2RlcCA8LSBmdW5jdGlvbihvYmplY3QsIGRhdGEsIGdyaWQpIHsKICAjIEluaXRpYWxpemUgYSB2ZWN0b3IgdG8gc2F2ZSB0aGUgZWZmZWN0CiAgcGRfZWZmZWN0IDwtIHJlcCgwLCBucm93KGdyaWQpKQogICMgSXRlcmF0ZSBvdmVyIHRoZSBncmlkIHZhbHVlcyB0byBjYWxjdWxhdGUgdGhlIGVmZmVjdAogIGZvciAoaSBpbiBzZXFfbGVuKGxlbmd0aChwZF9lZmZlY3QpKSkgewogICAgcGRfZWZmZWN0W2ldIDwtIAogICAgICBkYXRhICU+JSAKICAgICAgZHBseXI6Om11dGF0ZSghISBuYW1lcyhncmlkKSA6PSBncmlkW2ksIF0pICU+JSAKICAgICAgcHJlZGljdF9tb2RlbChvYmplY3QsIG5ld2RhdGEgPSAuKSAlPiUgCiAgICAgIG1lYW4oKQogIH0KICByZXR1cm4ocGRfZWZmZWN0KQp9CmBgYAoKV2Ugd2lsbCBub3cgdXNlIHRoaXMgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgdGhlIFBEIGVmZmVjdCBmb3IgdGhlIGFnZSBvZiB0aGUgcG9saWN5aG9sZGVyIGluIHRoZSBmcmVxdWVuY3kgbW9kZWxzOgoKYGBge3IgcGRwX2RhdGEsIHdhcm5pbmcgPSBGQUxTRX0KIyBVc2UgYSByYW5kb20gc2FtcGxlIG9mIHRoZSB0cmFpbmluZyBvYnNlcnZhdGlvbnMKc2V0LnNlZWQoNTQzMjEpCm10cGxfdHJuX3NhbXBsZSA8LSBtdHBsX3RybltzYW1wbGUoc2VxX2xlbihucm93KG10cGxfdHJuKSksIHNpemUgPSAxMDAwMCksIF0KIyBEZWZpbmUgdGhlIGdyaWQgZm9yIHRoZSBhZ2VzCmdyaWRfYWdlcGggPC0gZGF0YS5mcmFtZSgnYWdlcGgnID0gMTg6OTApCiMgQ2FsY3VsYXRlIHRoZSBQRCBlZmZlY3QgZm9yIGVhY2ggTUwgbW9kZWwKZ3JpZF9hZ2VwaCA8LSBncmlkX2FnZXBoICU+JSAKICBkcGx5cjo6bXV0YXRlKHRyZWUgPSB0cmVlX2ZyZXEgJT4lIHBhcl9kZXAoZGF0YSA9IG10cGxfdHJuX3NhbXBsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZCA9IGdyaWRfYWdlcGgpLAogICAgICAgICAgICAgICAgcmYgPSByZl9mcmVxICU+JSBwYXJfZGVwKGRhdGEgPSBtdHBsX3Rybl9zYW1wbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZCA9IGdyaWRfYWdlcGgpLAogICAgICAgICAgICAgICAgZ2JtID0gZ2JtX2ZyZXEgJT4lIHBhcl9kZXAoZGF0YSA9IG10cGxfdHJuX3NhbXBsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWQgPSBncmlkX2FnZXBoKSkKCmBgYAoKQWZ0ZXIgc29tZSByZXNoYXBpbmcgd2UgY2FuIHBsb3QgdGhlc2UgZWZmZWN0cyBvbiB0b3Agb2YgZWFjaCBvdGhlci4gVGhlIGVmZmVjdHMgZm9yIHRoZSB0cmVlIGFuZCB0aGUgZ2JtIGNvcnJlc3BvbmQgdG8gdGhlIGdyZWVuIGxpbmVzIGluIHRoZSBib3R0b20gcGFuZWxzIG9mIEZpZ3VyZSA2LgoKYGBge3IgcGRwX3Bsb3QsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQ9NH0KZ3JpZF9hZ2VwaCAlPiUgcmVzaGFwZTI6Om1lbHQoaWQudmFycyA9ICdhZ2VwaCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAncGQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZS5uYW1lID0gJ21ldGhvZCcpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhZ2VwaCwgeSA9IHBkKSkgKyAKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gbWV0aG9kLCBjb2xvdXIgPSBtZXRob2QpKQpgYGAKCgojIyMgSUNFcwpBbiBpbmRpdmlkdWFsIGNvbmRpdGlvbmFsIGV4cGVjdGF0aW9uIChJQ0UpIGN1cnZlIGlzIGdlbmVyYXRlZCBpbiBhIHZlcnkgY29tcGFyYWJsZSB3YXkgdG8gYSBQRFAuIFRoZSBzYW1lIHN0ZXBzIGFzIGxpc3RlZCBhYm92ZSBhcmUgZm9sbG93ZWQsIG9ubHkgdGhlIGxhc3Qgc3RlcCBpcyBub3QgcGVyZm9ybWVkLiBBbiBJQ0UgY3VydmUgc2hvd3MgdGhlIGluZGl2aWR1YWwgcHJlZGljdGlvbnMgaW5zdGVhZCBvZiBhdmVyYWdpbmcgYWxsIHRoZSBwcmVkaWN0aW9ucyAobGlrZSBpbiBhIFBEUCkuIFRoZSBmdW5jdGlvbiBgaWNlYCB0byBnZW5lcmF0ZSBhbiBJQ0UgY3VydmUgaXMgdGhlcmVmb3JlIHZlcnkgc2ltaWxhciB0byBgcGFyX2RlcGA6CgpgYGB7ciBpY2VfZnVufQppY2UgPC0gZnVuY3Rpb24ob2JqZWN0LCBkYXRhLCBncmlkKSB7CiAgIyBJbml0aWFsaXplIGEgbWF0cml4IHRvIHNhdmUgdGhlIGVmZmVjdAogIGljZV9lZmZlY3QgPC0gbWF0cml4KDAsIG5yb3cgPSBucm93KGdyaWQpLCBuY29sID0gbnJvdyhkYXRhKSkKICAjIEl0ZXJhdGUgb3ZlciB0aGUgZ3JpZCB2YWx1ZXMgdG8gY2FsY3VsYXRlIHRoZSBlZmZlY3QKICBmb3IgKGkgaW4gc2VxX2xlbihucm93KGljZV9lZmZlY3QpKSkgewogICAgaWNlX2VmZmVjdFtpLCBdIDwtIAogICAgICBkYXRhICU+JSAKICAgICAgZHBseXI6Om11dGF0ZSghISBuYW1lcyhncmlkKSA6PSBncmlkW2ksIF0pICU+JSAKICAgICAgcHJlZGljdF9tb2RlbChvYmplY3QsIG5ld2RhdGEgPSAuKQogIH0KICByZXR1cm4oY2JpbmQoZ3JpZCwgaWNlX2VmZmVjdCkpCn0KYGBgCgpXZSB3aWxsIG5vdyB1c2UgdGhpcyBmdW5jdGlvbiB0byBnZW5lcmF0ZSB0aGUgSUNFIGVmZmVjdCBmb3IgdGhlIGJvbnVzLW1hbHVzIGxldmVsIGluIGZyZXF1ZW5jeSBtb2RlbHM6CgpgYGB7ciBpY2VfZGF0YSwgd2FybmluZyA9IEZBTFNFfQojIFVzZSBhIHJhbmRvbSBzYW1wbGUgb2YgdGhlIHRyYWluaW5nIG9ic2VydmF0aW9ucwpzZXQuc2VlZCg1NDMyMSkKbXRwbF90cm5fc2FtcGxlIDwtIG10cGxfdHJuW3NhbXBsZShzZXFfbGVuKG5yb3cobXRwbF90cm4pKSwgc2l6ZSA9IDEwMDApLCBdCiMgRGVmaW5lIHRoZSBncmlkIGZvciB0aGUgYWdlcwpncmlkX2JtIDwtIGRhdGEuZnJhbWUoJ2JtJyA9IDA6MjIpCiMgQ2FsY3VsYXRlIHRoZSBJQ0UgZWZmZWN0CmljZV90cmVlIDwtIHRyZWVfZnJlcSAlPiUgaWNlKGRhdGEgPSBtdHBsX3Rybl9zYW1wbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWQgPSBncmlkX2JtKQppY2VfZ2JtIDwtIGdibV9mcmVxICU+JSBpY2UoZGF0YSA9IG10cGxfdHJuX3NhbXBsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWQgPSBncmlkX2JtKQpgYGAKCkFmdGVyIHNvbWUgcmVzaGFwaW5nIHdlIHBsb3QgdGhlc2UgSUNFIGN1cnZlcyB3aXRoIHRoZSBQRCBlZmZlY3Qgb24gdG9wLCBhcyBpbiBGaWd1cmUgOCBvZiB0aGUgcGFwZXI6CgpgYGB7ciBpY2VfcGxvdCwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodD00fQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSgKICBpY2VfdHJlZSAlPiUgcmVzaGFwZTI6Om1lbHQoaWQudmFycyA9ICdibScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAnaWNlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICdvYnNlcnZhdGlvbicpICU+JQogIGRwbHlyOjpncm91cF9ieShibSkgJT4lIAogIGRwbHlyOjptdXRhdGUocGQgPSBtZWFuKGljZSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBibSkpICsgCiAgZ2VvbV9saW5lKGFlcyh5ID0gaWNlLCBncm91cCA9IG9ic2VydmF0aW9uKSwgY29sb3IgPSAnZ3JleScsIGFscGhhID0gMC4xKSArIAogIGdlb21fbGluZShhZXMoeSA9IHBkKSwgc2l6ZSA9IDEsIGNvbG9yID0gJ25hdnknKSwKICAKICBpY2VfZ2JtICU+JSByZXNoYXBlMjo6bWVsdChpZC52YXJzID0gJ2JtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZS5uYW1lID0gJ2ljZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICdvYnNlcnZhdGlvbicpICU+JQogIGRwbHlyOjpncm91cF9ieShibSkgJT4lIAogIGRwbHlyOjptdXRhdGUocGQgPSBtZWFuKGljZSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBibSkpICsgCiAgZ2VvbV9saW5lKGFlcyh5ID0gaWNlLCBncm91cCA9IG9ic2VydmF0aW9uKSwgY29sb3IgPSAnZ3JleScsIGFscGhhID0gMC4xKSArIAogIGdlb21fbGluZShhZXMoeSA9IHBkKSwgc2l6ZSA9IDEsIGNvbG9yID0gJ25hdnknKSwKICAKICBuY29sID0gMgopCmBgYAoKIyMgSW50ZXJhY3Rpb24gZWZmZWN0cwpUcmVlLWJhc2VkIG1vZGVscyBhcmUgb2Z0ZW4gcHJhaXNlZCBmb3IgdGhlaXIgYWJpbGl0eSB0byBkZXRlY3QgaW50ZXJhY3Rpb24gZWZmZWN0cyBiZXR3ZWVuIHZhcmlhYmxlcy4gRnJpZWRtYW7igJlzICpIKi1zdGF0aXN0aWMgZXN0aW1hdGVzIHRoZSBpbnRlcmFjdGlvbiBzdHJlbmd0aCBieSBtZWFzdXJpbmcgaG93IG11Y2ggb2YgdGhlIHByZWRpY3Rpb24gdmFyaWFuY2Ugb3JpZ2luYXRlcyBmcm9tIHRoZSBpbnRlcmFjdGlvbiwgc2VlIFNlY3Rpb24gNC40IGluIHRoZSBwYXBlciBmb3IgdGhlIGRldGFpbHMuIFRoZSBmdW5jdGlvbiBgaW50ZXJhY3QuZ2JtYCBjYWxjdWxhdGVzIHRoZSAqSCotc3RhdGlzdGljIGZvciBhIGBnYm1gIG9iamVjdC4gKCpOb3RlOiB0aGUgZnVuY3Rpb24gYGludGVyYWN0LmdibWAgaXMgbm90IGV4cG9ydGVkIGluIEhhcnJ5IFNvdXRod29ydGgncyB2ZXJzaW9uLCBzbyBJIGluY2x1ZGUgdGhlIGZ1bmN0aW9uIGluIHRoZSBSbWQgc291cmNlIG9mIHRoaXMgTm90ZWJvb2suKikKCmBgYHtyIGludGVyYWN0X2Z1biwgaW5jbHVkZT1GQUxTRX0KaW50ZXJhY3QuZ2JtIDwtIGZ1bmN0aW9uKHgsIGRhdGEsIGkudmFyID0gMSwgbi50cmVlcyA9IHgkbi50cmVlcyl7CiAgICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICAjIERvIHNhbml0eSBjaGVja3Mgb24gdGhlIGNhbGwKICAgIGlmICh4JGludGVyYWN0aW9uLmRlcHRoIDwgbGVuZ3RoKGkudmFyKSl7CiAgICAgICBzdG9wKCJpbnRlcmFjdGlvbi5kZXB0aCB0b28gbG93IGluIG1vZGVsIGNhbGwiKQogICB9CgogICBpZiAoYWxsKGlzLmNoYXJhY3RlcihpLnZhcikpKXsKICAgICAgaSA8LSBtYXRjaChpLnZhciwgeCR2YXIubmFtZXMpCiAgICAgIGlmIChhbnkoaXMubmEoaSkpKSB7CiAgICAgICAgIHN0b3AoIlZhcmlhYmxlcyBnaXZlbiBhcmUgbm90IHVzZWQgaW4gZ2JtIG1vZGVsIGZpdDogIiwgaS52YXJbaXMubmEoaSldKQogICAgICB9CiAgICAgIGVsc2UgewogICAgICAgICBpLnZhciA8LSBpCiAgICAgIH0KICAgfQogICBpZiAoKG1pbihpLnZhcikgPCAxKSB8fCAobWF4KGkudmFyKSA+IGxlbmd0aCh4JHZhci5uYW1lcykpKSB7CiAgICAgIHdhcm5pbmcoImkudmFyIG11c3QgYmUgYmV0d2VlbiAxIGFuZCAiLCBsZW5ndGgoeCR2YXIubmFtZXMpKQogICB9CiAgIGlmIChuLnRyZWVzID4geCRuLnRyZWVzKSB7CiAgICAgIHdhcm5pbmcocGFzdGUoIm4udHJlZXMgZXhjZWVkcyB0aGUgbnVtYmVyIG9mIHRyZWVzIGluIHRoZSBtb2RlbCwgIiwKICAgICAgICAgICAgICAgICAgICB4JG4udHJlZXMsIi4gVXNpbmcgIiwgeCRuLnRyZWVzLCAiIHRyZWVzLiIsIHNlcCA9ICIiKSkKICAgICAgbi50cmVlcyA8LSB4JG4udHJlZXMKICAgfQogICAjIEVuZCBvZiBzYW5pdHkgY2hlY2tzCiAgICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICAgdW5pcXVlLnRhYiA8LSBmdW5jdGlvbih6LGkudmFyKSB7CiAgICAgIGEgPC0gdW5pcXVlKHpbLGkudmFyLGRyb3A9RkFMU0VdKQogICAgICBhJG4gPC0gdGFibGUoZmFjdG9yKGFwcGx5KHpbLGkudmFyLGRyb3A9RkFMU0VdLDEscGFzdGUsY29sbGFwc2U9IlxyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWFwcGx5KGEsMSxwYXN0ZSxjb2xsYXBzZT0iXHIiKSkpCiAgICAgIHJldHVybihhKQogICB9CgogICAjIGNvbnZlcnQgZmFjdG9ycwogICBmb3IoaiBpbiBpLnZhcikgewogICAgICBpZihpcy5mYWN0b3IoZGF0YVsseCR2YXIubmFtZXNbal1dKSkKICAgICAgZGF0YVsseCR2YXIubmFtZXNbal1dIDwtCiAgICAgIGFzLm51bWVyaWMoZGF0YVsseCR2YXIubmFtZXNbal1dKS0xCiAgIH0KCiAgICMgZ2VuZXJhdGUgYSBsaXN0IHdpdGggYWxsIGNvbWJpbmF0aW9ucyBvZiB2YXJpYWJsZXMKICAgYSA8LSBhcHBseShleHBhbmQuZ3JpZChyZXAobGlzdChjKEZBTFNFLFRSVUUpKSwgbGVuZ3RoKGkudmFyKSkpWy0xLF0sMSwKICAgICAgICAgICAgICBmdW5jdGlvbih4KSBhcy5udW1lcmljKHdoaWNoKHgpKSkKICAgRkYgPC0gdmVjdG9yKCJsaXN0IixsZW5ndGgoYSkpCiAgIGZvcihqIGluIDE6bGVuZ3RoKGEpKSB7CiAgICAgIEZGW1tqXV0kWiA8LSBkYXRhLmZyYW1lKHVuaXF1ZS50YWIoZGF0YSwgeCR2YXIubmFtZXNbaS52YXJbYVtbal1dXV0pKQogICAgICBGRltbal1dJG4gPC0gYXMubnVtZXJpYyhGRltbal1dJFokbikKICAgICAgRkZbW2pdXSRaJG4gPC0gTlVMTAogICAgICBGRltbal1dJGYgPC0gLkNhbGwoImdibV9wbG90IiwKICAgICAgICAgICAgICAgICAgICAgICAgIFggPSBhcy5kb3VibGUoZGF0YS5tYXRyaXgoRkZbW2pdXSRaKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBjUm93cyA9IGFzLmludGVnZXIobnJvdyhGRltbal1dJFopKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGNDb2xzID0gYXMuaW50ZWdlcihuY29sKEZGW1tqXV0kWikpLAogICAgICAgICAgICAgICAgICAgICAgICAgbi5jbGFzcyA9IGFzLmludGVnZXIoeCRudW0uY2xhc3NlcyksCiAgICAgICAgICAgICAgICAgICAgICAgICBpLnZhciA9IGFzLmludGVnZXIoaS52YXJbYVtbal1dXSAtIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgbi50cmVlcyA9IGFzLmludGVnZXIobi50cmVlcyksCiAgICAgICAgICAgICAgICAgICAgICAgICBpbml0RiA9IGFzLmRvdWJsZSh4JGluaXRGKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVzID0geCR0cmVlcywKICAgICAgICAgICAgICAgICAgICAgICAgIGMuc3BsaXRzID0geCRjLnNwbGl0cywKICAgICAgICAgICAgICAgICAgICAgICAgIHZhci50eXBlID0gYXMuaW50ZWdlcih4JHZhci50eXBlKSwKICAgICAgICAgICAgICAgICAgICAgICAgIFBBQ0tBR0UgPSAiZ2JtIikKICAgICAgIyBGRltbampdXSRaIGlzIHRoZSBkYXRhLCBmIGlzIHRoZSBwcmVkaWN0aW9ucywgbiBpcyB0aGUgbnVtYmVyIG9mIGxldmVscyBmb3IgZmFjdG9ycwoKICAgICAgIyBOZWVkIHRvIHJlc3RydWN0dXJlIGYgdG8gZGVhbCB3aXRoIG11bHRpbm9taWFsIGNhc2UKICAgICAgRkZbW2pdXSRmIDwtIG1hdHJpeChGRltbal1dJGYsIG5jb2w9eCRudW0uY2xhc3NlcywgYnlyb3c9RkFMU0UpCgogICAgICAjIGNlbnRlciB0aGUgdmFsdWVzCiAgICAgIEZGW1tqXV0kZiA8LSBhcHBseShGRltbal1dJGYsIDIsIGZ1bmN0aW9uKHgsIHcpewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4IC0gd2VpZ2h0ZWQubWVhbih4LCB3LCBuYS5ybT1UUlVFKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LCB3PUZGW1tqXV0kbikKCiAgICAgICMgcHJlY29tcHV0ZSB0aGUgc2lnbiBvZiB0aGVzZSB0ZXJtcyB0byBhcHBlYXIgaW4gSAogICAgICBGRltbal1dJHNpZ24gPC0gaWZlbHNlKGxlbmd0aChhW1tqXV0pICUlIDIgPT0gbGVuZ3RoKGkudmFyKSAlJSAyLCAxLCAtMSkKICAgfQoKICAgSCA8LSBGRltbbGVuZ3RoKGEpXV0kZgoKICAgZm9yKGogaW4gMToobGVuZ3RoKGEpLTEpKXsKICAgICAgaTEgPC0gYXBwbHkoRkZbW2xlbmd0aChhKV1dJFpbLGFbW2pdXSwgZHJvcD1GQUxTRV0sIDEsIHBhc3RlLCBjb2xsYXBzZT0iXHIiKQogICAgICBpMiA8LSBhcHBseShGRltbal1dJFosMSxwYXN0ZSxjb2xsYXBzZT0iXHIiKQogICAgICBpIDwtIG1hdGNoKGkxLCBpMikKCiAgICAgIEggPC0gSCArIHdpdGgoRkZbW2pdXSwgc2lnbipmW2ksXSkKICAgfQoKICAgIyBDb21wdXRlIEgKICAgdyA8LSBtYXRyaXgoRkZbW2xlbmd0aChhKV1dJG4sIG5jb2w9MSkKICAgZiA8LSBtYXRyaXgoRkZbW2xlbmd0aChhKV1dJGZeMiwgbmNvbD14JG51bS5jbGFzc2VzLCBieXJvdz1GQUxTRSkKCiAgIHRvcCA8LSBhcHBseShIXjIsIDIsIHdlaWdodGVkLm1lYW4sIHcgPSB3LCBuYS5ybSA9IFRSVUUpCiAgIGJ0bSA8LSBhcHBseShmLCAyLCB3ZWlnaHRlZC5tZWFuLCB3ID0gdywgbmEucm0gPSBUUlVFKQogICBIIDwtIHRvcCAvIGJ0bQoKICAgaWYgKHgkZGlzdHJpYnV0aW9uJG5hbWU9PSJtdWx0aW5vbWlhbCIpewogICAgICBuYW1lcyhIKSA8LSB4JGNsYXNzZXMKICAgfQoKICAgIyBJZiBIID4gMSwgcm91bmRpbmcgYW5kIHRpbnkgbWFpbiBlZmZlY3RzIGhhdmUgbWVzc2VkIHRoaW5ncyB1cAogICBIW0ggPiAxXSA8LSBOYU4KCiAgIHJldHVybihzcXJ0KEgpKQp9CmBgYAoKV2Ugbm93IGNhbGN1bGF0ZSB0d28td2F5IGludGVyYWN0aW9uIHN0cmVuZ3RocyBiZXR3ZWVuIHZhcmlhYmxlcyBhbmQgdmVyaWZ5IHNvbWUgdmFsdWVzIGluIFRhYmxlIDQgb2YgdGhlIHBhcGVyOgoKYGBge3IgaW50ZXJhY3RfZ2JtfQpnYm1fZnJlcSAlPiUgaW50ZXJhY3QuZ2JtKGRhdGEgPSBtdHBsX3RybiwKICAgICAgICAgICAgICAgICAgICAgICAgICBpLnZhciA9IGMoJ2Z1ZWwnLCAncG93ZXInKSkgJT4lIHJvdW5kKDQpCmdibV9mcmVxICU+JSBpbnRlcmFjdC5nYm0oZGF0YSA9IG10cGxfdHJuLAogICAgICAgICAgICAgICAgICAgICAgICAgIGkudmFyID0gYygnYWdlcGgnLCAnc2V4JykpICU+JSByb3VuZCg0KQpnYm1fZnJlcSAlPiUgaW50ZXJhY3QuZ2JtKGRhdGEgPSBtdHBsX3RybiwKICAgICAgICAgICAgICAgICAgICAgICAgICBpLnZhciA9IGMoJ2FnZWMnLCAnY292ZXJhZ2UnKSkgJT4lIHJvdW5kKDQpCmdibV9mcmVxICU+JSBpbnRlcmFjdC5nYm0oZGF0YSA9IG10cGxfdHJuLAogICAgICAgICAgICAgICAgICAgICAgICAgIGkudmFyID0gYygnYWdlcGgnLCAncG93ZXInKSkgJT4lIHJvdW5kKDQpCmBgYAoKV2UgdXNlIHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2UgZWZmZWN0cyBvZiBhIHZhcmlhYmxlLCBncm91cGVkIGJ5IGFub3RoZXIgdmFyaWFibGUsIHRvIGdldCBhbiBpbnNpZ2h0IG9uIHRoZSBpbnRlcmFjdGlvbiBiZWhhdmlvciBiZXR3ZWVuIHRob3NlIHR3byB2YXJpYWJsZXMuIFRoZSBmdW5jdGlvbiBgcGFyX2RlcF9ieWAgYWxsb3dzIHRvIGdlbmVyYXRlIHN1Y2ggZ3JvdXBlZCBQRCBlZmZlY3RzOgoKYGBge3IgcGRwX2J5X2Z1bn0KcGFyX2RlcF9ieSA8LSBmdW5jdGlvbihvYmplY3QsIGRhdGEsIGdyaWQsIGJ5X3Zhciwgbmdyb3VwcyA9IE5VTEwpIHsKICAjIEluaXRpYWxpemUgYSBtYXRyaXggdG8gc2F2ZSB0aGUgZWZmZWN0CiAgaWNlX2VmZmVjdCA8LSBtYXRyaXgoMCwgbnJvdyA9IG5yb3coZGF0YSksIG5jb2wgPSBucm93KGdyaWQpKQogICMgSXRlcmF0ZSBvdmVyIHRoZSBncmlkIHZhbHVlcyB0byBjYWxjdWxhdGUgdGhlIGVmZmVjdAogIGZvciAoaSBpbiBzZXFfbGVuKG5jb2woaWNlX2VmZmVjdCkpKSB7CiAgICBpY2VfZWZmZWN0WywgaV0gPC0gCiAgICAgIGRhdGEgJT4lIAogICAgICBkcGx5cjo6bXV0YXRlKCEhIG5hbWVzKGdyaWQpIDo9IGdyaWRbaSwgXSkgJT4lIAogICAgICBwcmVkaWN0X21vZGVsKG9iamVjdCwgbmV3ZGF0YSA9IC4pCiAgfQogICMgQWRkIHRoZSBncm91cGluZyB2YXJpYWJsZSB0byB0aGUgZWZmZWN0CiAgcGRfZ3IgPC0gZGF0YSAlPiUgZHBseXI6OnNlbGVjdCghISBieV92YXIpICU+JSAKICAgIGNiaW5kKGljZV9lZmZlY3QpCiAgIyBCaW4gdGhlIGdyb3VwaW5nIHZhcmlhYmxlIGluIGdyb3VwcwogIGlmICghaXMubnVsbChuZ3JvdXBzKSkgewogICAgYmlucyA8LSBkYXRhICU+JSBkcGx5cjo6cHVsbCghISBieV92YXIpICU+JSAKICAgICAgY3V0KGJyZWFrcyA9IHVuaXF1ZShxdWFudGlsZSguLCBwcm9icyA9IHNlcSgwLCAxLCAxL25ncm91cHMpKSksCiAgICAgICAgICBpbmNsdWRlLmxvd2VzdCA9IFRSVUUsIGRpZy5sYWIgPSA0KQogICAgcGRfZ3IgPC0gcGRfZ3IgJT4lIGRwbHlyOjptdXRhdGUoISEgYnlfdmFyIDo9IGJpbnMpCiAgfQogICMgQ2FsY3VsYXRlIHRoZSBQRCBlZmZlY3QgZm9yIGVhY2ggZ3JvdXAKICBwZF9nciA8LSBwZF9nciAlPiUgZHBseXI6Omdyb3VwX2J5X2F0KGJ5X3ZhcikgJT4lIAogICAgZHBseXI6OnN1bW1hcmlzZV9hbGwobWVhbikgJT4lIAogICAgZHBseXI6OnJlbmFtZShzZXROYW1lcyhhcy5jaGFyYWN0ZXIoc2VxX2xlbihucm93KGdyaWQpKSksIHVubGlzdChncmlkKSkpCiAgIyBDZW50ZXIgdGhlIFBEIGVmZmVjdHMgdG8gc3RhcnQgZnJvbSB6ZXJvCiAgcGRfZ3JbMjpuY29sKHBkX2dyKV0gPC0gcGRfZ3JbMjpuY29sKHBkX2dyKV0gLSBwZF9ncltbMl1dCiAgCiAgcmV0dXJuKHBkX2dyKQp9CmBgYAoKV2UgY2FsY3VsYXRlIHRoZSBQRCBlZmZlY3QgZm9yIGBwb3dlcmAsIGdyb3VwZWQgYnkgYGFnZXBoYCBhbmQgYGZ1ZWxgLiBOb3RlIHRoYXQgd2UgdXNlIGBuZ3JvdXBzID0gNWAgZm9yIHRoZSBjb250aW51b3VzIHZhcmlhYmxlIGBhZ2VwaGAgYnV0IGBuZ3JvdXBzID0gTlVMTGAgZm9yIHRoZSBmYWN0b3IgdmFyaWFibGUgYGZ1ZWxgIChvbmUgZ3JvdXAgZm9yIGVhY2ggZmFjdG9yIGxldmVsKS4KCmBgYHtyIHBkcF9ieV9kYXRhLCB3YXJuaW5nPUZBTFNFfQpwZF9wb3dlcl9hZ2VwaCA8LSBnYm1fZnJlcSAlPiUgcGFyX2RlcF9ieShkYXRhID0gbXRwbF90cm5fc2FtcGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmlkID0gZGF0YS5mcmFtZSgncG93ZXInID0gMzA6MTUwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlfdmFyID0gJ2FnZXBoJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmdyb3VwcyA9IDUpCgpwZF9wb3dlcl9mdWVsIDwtIGdibV9mcmVxICU+JSBwYXJfZGVwX2J5KGRhdGEgPSBtdHBsX3Rybl9zYW1wbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZCA9IGRhdGEuZnJhbWUoJ3Bvd2VyJyA9IDMwOjE1MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlfdmFyID0gJ2Z1ZWwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ncm91cHMgPSBOVUxMKQpgYGAKCldlIHZpc3VhbGl6ZSB0aGUgaW50ZXJhY3Rpb24gYmVoYXZpb3IgYmV0d2VlbiBgcG93ZXJgIGFuZCBib3RoIGBhZ2VwaGAgYW5kIGBmdWVsYC4gTW9yZSBncmFwaHMgYXJlIGF2YWlsYWJsZSBpbiBGaWd1cmUgOSBpbiB0aGUgcGFwZXIuICgqTm90ZTogdGhlIGJpbiBsYWJlbHMgZm9yIGBhZ2VwaGAgZGlmZmVyIGJlY2F1c2UgYSBzbWFsbGVyIHNhbXBsZSBzaXplIGlzIHVzZWQgaW4gdGhpcyBOb3RlYm9vay4qKQoKYGBge3IgcGRwX2J5X3Bsb3QsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKAogIHBkX3Bvd2VyX2FnZXBoICU+JSByZXNoYXBlMjo6bWVsdChpZC52YXIgPSAnYWdlcGgnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUubmFtZSA9ICdwZF9ncm91cCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZS5uYW1lID0gJ3Bvd2VyJykgJT4lIAogIGRwbHlyOjptdXRhdGUocG93ZXIgPSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihwb3dlcikpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcG93ZXIsIHkgPSBwZF9ncm91cCkpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gYWdlcGgsIGNvbG91ciA9IGFnZXBoKSksCiAgCiAgcGRfcG93ZXJfZnVlbCAlPiUgcmVzaGFwZTI6Om1lbHQoaWQudmFyID0gJ2Z1ZWwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAncGRfZ3JvdXAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlLm5hbWUgPSAncG93ZXInKSAlPiUgCiAgZHBseXI6Om11dGF0ZShwb3dlciA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHBvd2VyKSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwb3dlciwgeSA9IHBkX2dyb3VwKSkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBmdWVsLCBjb2xvdXIgPSBmdWVsKSksCiAgCiAgbmNvbCA9IDIKKQoKYGBgCgojIyBTdGF0aXN0aWNhbCBwZXJmb3JtYW5jZQoKQWZ0ZXIgZ2FpbmluZyBzb21lIGluc2lnaHRzIGZyb20gdGhlIGRpZmZlcmVudCBNTCBtb2RlbHMsIHdlIG5vdyBwdXQgZm9jdXMgb24gY29tcGFyaW5nIHRoZSBvdXQtb2Ytc2FtcGxlIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0IGRhdGEgYG10cGxfdHN0YC4gV2UgcHJlZGljdCBlYWNoIE1MIG1vZGVsIG9uIHRoZSB0ZXN0IGRhdGE6CgpgYGB7ciBwcmVkX2RhdGEsIHdhcm5pbmc9RkFMU0V9Cm9vc19wcmVkIDwtIHRpYmJsZTo6dGliYmxlKAogIHRyZWVfZnJlcSA9IHRyZWVfZnJlcSAlPiUgcHJlZGljdF9tb2RlbChuZXdkYXRhID0gbXRwbF90c3QpLAogIHJmX2ZyZXEgPSByZl9mcmVxICU+JSBwcmVkaWN0X21vZGVsKG5ld2RhdGEgPSBtdHBsX3RzdCksCiAgZ2JtX2ZyZXEgPSBnYm1fZnJlcSAlPiUgcHJlZGljdF9tb2RlbChuZXdkYXRhID0gbXRwbF90c3QpLAogIHRyZWVfc2V2ID0gdHJlZV9zZXYgJT4lIHByZWRpY3RfbW9kZWwobmV3ZGF0YSA9IG10cGxfdHN0KSwKICByZl9zZXYgPSByZl9zZXYgJT4lIHByZWRpY3RfbW9kZWwobmV3ZGF0YSA9IG10cGxfdHN0KSwKICBnYm1fc2V2ID0gZ2JtX3NldiAlPiUgcHJlZGljdF9tb2RlbChuZXdkYXRhID0gbXRwbF90c3QpCikKYGBgCgpUaGVzZSBwcmVkaWN0aW9ucyBhcmUgY29tcGFyZWQgdG8gdGhlIG9ic2VydmVkIHZhbHVlcyBpbiBgbXRwbF90c3RgIHdpdGggdGhlIFBvaXNzb24vZ2FtbWEgZGV2aWFuY2UgZm9yIGZyZXF1ZW5jeS9zZXZlcml0eSBtb2RlbHMgcmVzcGVjdGl2ZWx5OgoKYGBge3IgZGV2X2Z1bn0KIyBQb2lzc29uIGRldmlhbmNlCmRldl9wb2lzcyA8LSBmdW5jdGlvbih5dHJ1ZSwgeWhhdCkgewogIC0yICogbWVhbihkcG9pcyh5dHJ1ZSwgeWhhdCwgbG9nID0gVFJVRSkgLSBkcG9pcyh5dHJ1ZSwgeXRydWUsIGxvZyA9IFRSVUUpLCBuYS5ybSA9IFRSVUUpCn0KIyBHYW1tYSBkZXZpYW5jZQpkZXZfZ2FtbWEgPC0gZnVuY3Rpb24oeXRydWUsIHloYXQsIHdjYXNlKSB7CiAgIC0yICogbWVhbih3Y2FzZSAqIChsb2coeXRydWUveWhhdCkgLSAoeXRydWUgLSB5aGF0KS95aGF0KSwgbmEucm0gPSBUUlVFKQp9CmBgYAoKVGhlIG91dC1vZi1zYW1wbGUgZGV2aWFuY2VzIGFyZSBjYWxjdWxhdGVkIGJlbG93LiBUaGVzZSBhcmUgdGhlIHZhbHVlcyBmb3IgZGF0YSBmb2xkIDMgaW4gRmlndXJlIDEwIG9mIHRoZSBwYXBlci4KCmBgYHtyIG9vc19jb21wfQojIENhbGN1bGF0ZSB0aGUgUG9pc3NvbiBkZXZpYW5jZSBmb3IgdGhlIGZyZXF1ZW5jeSBtb2RlbHMKb29zX3ByZWQgJT4lIGRwbHlyOjpzZWxlY3QoZW5kc193aXRoKCdfZnJlcScpKSAlPiUgCiAgcHVycnI6Om1hcCh+IGRldl9wb2lzcyhtdHBsX3RzdCRuY2xhaW1zLCAueCAqIG10cGxfdHN0JGV4cG8pKQojIENhbGN1bGF0ZSB0aGUgZ2FtbWEgZGV2aWFuY2UgZm9yIHRoZSBzZXZlcml0eSBtb2RlbHMKb29zX3ByZWQgJT4lIGRwbHlyOjpzZWxlY3QoZW5kc193aXRoKCdfc2V2JykpICU+JSAKICBwdXJycjo6bWFwKH4gZGV2X2dhbW1hKG10cGxfdHN0JGF2ZXJhZ2UsIC54LCBtdHBsX3RzdCRuY2xhaW1zKSkKYGBgCgojIyBFY29ub21pYyBsaWZ0CgpBZnRlciBjb21wYXJpbmcgdGhlIE1MIG1vZGVscyBmb3IgZnJlcXVlbmN5IGFuZCBzZXZlcml0eSwgd2Ugbm93IHR1cm4gdG8gYSBjb21wYXJpc29uIGF0IHRoZSBwcmVtaXVtIGxldmVsLiBXZSBjYWxjdWxhdGUgdGhlIHByZWRpY3RlZCBwcmVtaXVtcyBmb3IgdGhlIHRlc3QgZGF0YSBgbXRwbF90c3RgIGJ5IG11bHRpcGx5aW5nIHRoZSBmcmVxdWVuY3kgYW5kIHNldmVyaXR5OgoKYGBge3IgcHJlbV9kYXRhfQpvb3NfcHJlZCA8LSBvb3NfcHJlZCAlPiUgZHBseXI6Om11dGF0ZSgKICB0cmVlX3ByZW0gPSB0cmVlX2ZyZXEgKiB0cmVlX3NldiwKICByZl9wcmVtID0gcmZfZnJlcSAqIHJmX3NldiwKICBnYm1fcHJlbSA9IGdibV9mcmVxICogZ2JtX3NldgopCmBgYAoKVGhlIHByZWRpY3RlZCBwcmVtaXVtIHRvdGFscyBmb3IgZWFjaCBtb2RlbCAoYWRqdXN0ZWQgZm9yIGV4cG9zdXJlKSBhcmUgY2FsY3VsYXRlZCBiZWxvdy4gVGhlc2UgY29ycmVzcG9uZCB0byB0aGUgdmFsdWVzIGluIFRhYmxlIDUgb2YgdGhlIHBhcGVyIGZvciBkYXRhIGZvbGQgMy4gKCpOb3RlOiB0aGUgdmFsdWVzIGZvciB0aGUgcmFuZG9tIGZvcmVzdCBhcmUgc2xpZ2hsdHkgZGlmZmVyZW50IGR1ZSB0byBhbiBpbXBsZW1lbnRhdGlvbiB1cGRhdGUgdG8gdGhlIGBkaXN0UmZvcmVzdGAgcGFja2FnZSByZWdhcmRpbmcgc3Vic2FtcGxpbmcuKikKCmBgYHtyIHByZW1fdG90YWx9Cm9vc19wcmVkICU+JSBkcGx5cjo6c2VsZWN0KGVuZHNfd2l0aCgnX3ByZW0nKSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2VfYWxsKH4gc3VtKC54ICogbXRwbF90c3QkZXhwbykpCmBgYAoKV2Ugbm93IGZvY3VzIG9uIHNvbWUgbW9kZWwgbGlmdCBtZWFzdXJlcywgd2hpY2ggYXJlIGludHJvZHVjZWQgYW5kIGFuYWx5emVkIGluIFNlY3Rpb25zIDUuMSBhbmQgNS4yIG9mIHRoZSBwYXBlci4gVG8gc3RyZWFtbGluZSB0aGUgY29kaW5nIHdlIGFkZCB0aGUgb2JzZXJ2ZWQgdGFyZ2V0IHZhbHVlcyBmcm9tIHRoZSB0ZXN0IGRhdGEgYG10cGxfdHN0YCB0byB0aGUgcHJlZGljdGlvbnMgZGF0YToKCmBgYHtyIGxpZnRfZGF0YX0Kb29zX3ByZWQgPC0gb29zX3ByZWQgJT4lIGRwbHlyOjptdXRhdGUoCiAgbmNsYWltcyA9IG10cGxfdHN0JG5jbGFpbXMsCiAgZXhwbyA9IG10cGxfdHN0JGV4cG8sCiAgYW1vdW50ID0gbXRwbF90c3QkYW1vdW50CikKYGBgCgoKIyMjIExvc3MgcmF0aW8gbGlmdApUaGUgbG9zcyByYXRpbyBsaWZ0IGlzIGFzc2Vzc2VkIGJ5IGFwcGx5aW5nIHRoZSBmb2xsb3dpbmcgc3RlcHM6CgorIHNvcnQgcG9saWNpZXMgZnJvbSBzbWFsbGVzdCB0byBsYXJnZXN0IHJlbGF0aXZpdHkKKyBiaW4gdGhlIHBvbGljaWVzIGluIGdyb3VwcyBvZiBlcXVhbCBleHBvc3VyZQorIGNhbGN1bGF0ZSB0aGUgbG9zcyByYXRpbyBpbiBlYWNoIGJpbiB1c2luZyB0aGUgYmVuY2htYXJrIHByZW1pdW0KCmBgYHtyIGxybF9mdW59Cmxvc3NfcmF0aW9fbGlmdCA8LSBmdW5jdGlvbihkYXRhLCBiZW5jaCwgY29tcCwgbmdyb3VwcykgewogIAogICMgQ2FsY3VsYXRlIHJlbGF0aXZpdHkgYW5kIHNvcnQgZnJvbSBzbWFsbCB0byBsYXJnZQogIGRhdGEgJT4lIGRwbHlyOjptdXRhdGUociA9IGdldChwYXN0ZTAoY29tcCwgJ19wcmVtJykpIC8gZ2V0KHBhc3RlMChiZW5jaCwgJ19wcmVtJykpKSAlPiUgCiAgICBkcGx5cjo6YXJyYW5nZShyKSAlPiUgCiAgICAjIEJpbiBpbiBncm91cHMgb2YgZXF1YWwgZXhwb3N1cmUKICAgIGRwbHlyOjptdXRhdGUoYmluID0gY3V0KGN1bXN1bShleHBvKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHN1bShleHBvKSAqICgwOm5ncm91cHMpIC8gbmdyb3VwcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IEZBTFNFKSkgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KGJpbikgJT4lIAogICAgZHBseXI6Om11dGF0ZShyX2xhYiA9IHBhc3RlMCgnWycsIHJvdW5kKG1pbihyKSwgMiksICcsJywgcm91bmQobWF4KHIpLCAyKSwgJ10nKSkgJT4lIAogICAgIyBDYWxjdWxhdGUgbG9zcyByYXRpbyBwZXIgYmluCiAgICBkcGx5cjo6c3VtbWFyaXNlKHJfbGFiID0gcl9sYWJbMV0sCiAgICAgICAgICAgICAgICAgICAgIGxvc3NfcmF0aW8gPSBzdW0oYW1vdW50KSAvIHN1bShnZXQocGFzdGUwKGJlbmNoLCAnX3ByZW0nKSkpLAogICAgICAgICAgICAgICAgICAgICBzdW1fZXhwbyA9IHN1bShleHBvKSkKICAKfQpgYGAKCldlIGNhbGN1bGF0ZSB0aGUgbG9zcyByYXRpbyBsaWZ0cyBmb3IgdGhlIHRyZWUgYXMgYmVuY2htYXJrIGFuZCBHQk0gYXMgY29tcGV0aXRvciBhbmQgdmljZSB2ZXJzYToKCmBgYHtyIGxybF9kYXRhfQpscmxfZ2JtX3RyZWUgPC0gb29zX3ByZWQgJT4lIGxvc3NfcmF0aW9fbGlmdChiZW5jaCA9ICd0cmVlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcCA9ICdnYm0nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZ3JvdXBzID0gNSkKbHJsX2dibV90cmVlCmxybF90cmVlX2dibSA8LSBvb3NfcHJlZCAlPiUgbG9zc19yYXRpb19saWZ0KGJlbmNoID0gJ2dibScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXAgPSAndHJlZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ncm91cHMgPSA1KQpscmxfdHJlZV9nYm0KYGBgCgpQbG90dGluZyB0aGVzZSByZXN1bHRzIG5leHQgdG8gZWFjaCBvdGhlciBjbGVhcmx5IHNob3dzIHRoYXQgdGhlIEdCTSBhbGlnbnMgdGhlIHJpc2sgYmV0dGVyIGNvbXBhcmVkIHRvIHRoZSB0cmVlOgoKYGBge3IgbHJsX3Bsb3QsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKAogIGxybF9nYm1fdHJlZSAlPiUgZ2dwbG90KGFlcyh4ID0gcl9sYWIsIHkgPSBsb3NzX3JhdGlvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArCiAgICBnZ3RpdGxlKCdjb21wOiBnYm0gLyBiZW5jaDogdHJlZScpLAogIAogIGxybF90cmVlX2dibSAlPiUgZ2dwbG90KGFlcyh4ID0gcl9sYWIsIHkgPSBsb3NzX3JhdGlvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArCiAgICBnZ3RpdGxlKCdjb21wOiB0cmVlIC8gYmVuY2g6IGdibScpLAogIAogIG5jb2wgPSAyCikKYGBgCgoKIyMjIERvdWJsZSBsaWZ0ClRoZSBkb3VibGUgbGlmdCBpcyBhc3Nlc3NlZCBieSBhcHBseWluZyB0aGUgZm9sbG93aW5nIHN0ZXBzOgoKKyBzb3J0IHBvbGljaWVzIGZyb20gc21hbGxlc3QgdG8gbGFyZ2VzdCByZWxhdGl2aXR5CisgYmluIHRoZSBwb2xpY2llcyBpbiBncm91cHMgb2YgZXF1YWwgZXhwb3N1cmUKKyBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgbG9zcyBhbW91bnQgYW5kIGF2ZXJhZ2UgcHJlbWl1bXMgKGNvbXAgJiBiZW5jaCkgaW4gZWFjaCBiaW4KKyBjYWxjdWxhdGUgdGhlIHBlcmNlbnRhZ2UgZXJyb3Igb2YgcHJlbWl1bSAoY29tcCAmIGJlbmNoKSB0byBsb3NzIGluIGVhY2ggYmluCgpgYGB7ciBkYmxfZnVufQpkb3VibGVfbGlmdCA8LSBmdW5jdGlvbihkYXRhLCBiZW5jaCwgY29tcCwgbmdyb3VwcykgewogIAogICMgQ2FsY3VsYXRlIHJlbGF0aXZpdHkgYW5kIHNvcnQgZnJvbSBzbWFsbCB0byBsYXJnZQogIGRhdGEgJT4lIGRwbHlyOjptdXRhdGUociA9IGdldChwYXN0ZTAoY29tcCwgJ19wcmVtJykpIC8gZ2V0KHBhc3RlMChiZW5jaCwgJ19wcmVtJykpKSAlPiUgCiAgICBkcGx5cjo6YXJyYW5nZShyKSAlPiUgCiAgICAgIyBCaW4gaW4gZ3JvdXBzIG9mIGVxdWFsIGV4cG9zdXJlCiAgICBkcGx5cjo6bXV0YXRlKGJpbiA9IGN1dChjdW1zdW0oZXhwbyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzdW0oZXhwbykgKiAoMDpuZ3JvdXBzKSAvIG5ncm91cHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBGQUxTRSkpICU+JSAKICAgIGRwbHlyOjpncm91cF9ieShiaW4pICU+JSAKICAgIGRwbHlyOjptdXRhdGUocl9sYWIgPSBwYXN0ZTAoJ1snLCByb3VuZChtaW4ociksIDIpLCAnLCcsIHJvdW5kKG1heChyKSwgMiksICddJykpICU+JSAKICAgICMgQ2FsY3VsYXRlIHBlcmNlbnRhZ2UgZXJyb3JzIGZvciBib3RoIHRhcmlmZnMKICAgIGRwbHlyOjpzdW1tYXJpc2Uocl9sYWIgPSByX2xhYlsxXSwKICAgICAgICAgICAgICAgICAgICAgZXJyb3JfY29tcCA9IG1lYW4oZ2V0KHBhc3RlMChjb21wLCAnX3ByZW0nKSkpIC8gbWVhbihhbW91bnQpIC0gMSwKICAgICAgICAgICAgICAgICAgICAgZXJyb3JfYmVuY2ggPSBtZWFuKGdldChwYXN0ZTAoYmVuY2gsICdfcHJlbScpKSkgLyBtZWFuKGFtb3VudCkgLSAxLAogICAgICAgICAgICAgICAgICAgICBzdW1fZXhwbyA9IHN1bShleHBvKSkKICAKfQpgYGAKCldlIGNhbGN1bGF0ZSB0aGUgZG91YmxlIGxpZnQgZm9yIHRoZSB0cmVlIGFzIGJlbmNobWFyayBhbmQgR0JNIGFzIGNvbXBldGl0b3I6CgpgYGB7ciBkYmxfZGF0YX0KZGJsX2dibV90cmVlIDwtIG9vc19wcmVkICU+JSBkb3VibGVfbGlmdChiZW5jaCA9ICd0cmVlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21wID0gJ2dibScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmdyb3VwcyA9IDUpCmRibF9nYm1fdHJlZQpgYGAKClBsb3R0aW5nIHRoZXNlIHJlc3VsdHMgY2xlYXJseSBzaG93cyB0aGF0IHRoZSBHQk0gYWxpZ25zIHRoZSByaXNrIGJldHRlciBjb21wYXJlZCB0byB0aGUgdHJlZToKCmBgYHtyIGRibF9wbG90LCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpkYmxfZ2JtX3RyZWUgJT4lIHJlc2hhcGUyOjptZWx0KGlkLnZhcnMgPSBjKCdyX2xhYicsICdiaW4nICwgJ3N1bV9leHBvJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUubmFtZSA9ICdwZXJjX2VycicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICd0YXJpZmYnKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcl9sYWIsIHkgPSBwZXJjX2VycikpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gdGFyaWZmLCBjb2xvdXIgPSB0YXJpZmYpKQpgYGAKCgojIyMgR2luaSBpbmRleAoKVGhlIGxhc3QgbWVhc3VyZSBmb3IgZWNvbm9taWMgbGlmdCB0aGF0IHdlIGFuYWx5emUgaXMgdGhlIEdpbmkgaW5kZXggb2J0YWluZWQgZnJvbSBhbiBvcmRlcmVkIExvcmVueiBjdXJ2ZS4gVGhlIGZ1bmN0aW9uIGBnaW5pKClgIGZyb20gdGhlIGBjcGxtYCBwYWNrYWdlIGFsbG93cyB0byBjYWxjdWxhdGUgR2luaSBpbmRpY2VzIGZvciBjb21wZXRpbmcgbW9kZWxzLiBUaGUgbWluaS1tYXggc3RyYXRlZ3kgc2VsZWN0cyB0aGUgR0JNIGFzIHRoZSBtb2RlbCB0aGF0IGlzIGxlYXN0IHZ1bG5lcmFibGUgdG8gYWx0ZXJuYXRpdmUgbW9kZWxzOgoKYGBge3IgZ2luaV9mdW4sIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoY3BsbSkKZ2luaShsb3NzID0gJ2Ftb3VudCcsCiAgICAgc2NvcmUgPSBwYXN0ZTAoYygndHJlZScsICdyZicsICdnYm0nKSwgJ19wcmVtJyksCiAgICAgZGF0YSA9IGFzLmRhdGEuZnJhbWUob29zX3ByZWQpKQpgYGAKCldlIGNhbiBwcm9ncmFtIHRoZSBtaW5pLW1heCBzdHJhdGVneSBleHBsaWNpdGx5IGFzIGZvbGxvd3MsIHdoaWNoIGdpdmVzIHRoZSByYW5raW5nIGBnYm0gPiByZiA+IHRyZWVgOgoKYGBge3IgZ2luaV9kYXRhfQpnaW5pKGxvc3MgPSAnYW1vdW50JywKICAgICBzY29yZSA9IHBhc3RlMChjKCd0cmVlJywgJ3JmJywgJ2dibScpLCAnX3ByZW0nKSwKICAgICBkYXRhID0gYXMuZGF0YS5mcmFtZShvb3NfcHJlZCkpICU+JSAKICBzbG90KCdnaW5pJykgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgZHBseXI6Om11dGF0ZShtYXhfZ2luaSA9IHBtYXgodHJlZV9wcmVtLCByZl9wcmVtLCBnYm1fcHJlbSkpICU+JSAKICBkcGx5cjo6bXV0YXRlKGJlbmNoID0gYygndHJlZScsICdyZicsICdnYm0nKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKG1heF9naW5pKQpgYGAKCipOb3RlOiB0aGUgdmFsdWVzIGRpZmZlciBmcm9tIHRob3NlIGluIFRhYmxlIDYgb2YgdGhlIHBhcGVyIGJlY2F1c2UgdGhlIGFuYWx5c2lzIGluIHRoaXMgTm90ZWJvb2sgdXNlcyBvbmx5IHRoZSBvdXQtb2Ytc2FtcGxlIG9ic2VydmF0aW9ucyBmcm9tICRcbWF0aGNhbHtEfV8zJCwgd2hpbGUgdGhlIHJlc3VsdHMgaW4gdGhlIHBhcGVyIHVzZSB0aGUgb3V0LW9mLXNhbXBsZSBkYXRhIGZyb20gYWxsIHNpeCBmb2xkcyAkXG1hdGhjYWx7RH1fMSQgdXAgdG8gJFxtYXRoY2Fse0R9XzYkLiBUaGUgY29uY2x1c2lvbnMgcmVtYWluIHRoZSBzYW1lIGhvd2V2ZXIuKgoKCiMjIENvbmNsdXNpb25zClRoaXMgTm90ZWJvb2sgcmVwbGljYXRlcyBtb3N0IG9mIHRoZSByZXN1bHRzIGZyb20gb3VyIHBhcGVyIG9uICJCb29zdGluZyBpbnNpZ2h0cyBpbiBpbnN1cmFuY2UgdGFyaWZmIHBsYW5zIHdpdGggdHJlZS1iYXNlZCBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZHMiLiBIb3BlZnVsbHkgdGhpcyBoZWxwcyB5b3UgdG8ganVtcHN0YXJ0IHlvdXIgdHJlZS1iYXNlZCBNTCBhbmFseXNpcyBmb3IgaW5zdXJhbmNlIHByaWNpbmcgb3IgcmVsYXRlZCBhcHBsaWNhdGlvbnMuIEhhcHB5IGNvZGluZyEK